{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 简单理解 RHF 含频极化率及其与 TD-HF 间的关系"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 创建日期：2020-01-02\n",
    ">\n",
    "> 最后修改：2020-06-10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "含频极化率在非线性光学中有所应用。这里指的频率是入射激发光频率，而非分子振动频率。含频极化率英文是 Frequency-Dependent Polarizability，也有时使用动态 Dynamic 替代含频 Frequency-Dependent；相对地，没有入射激发光给出的极化率称为静态 Static 极化率。\n",
    "\n",
    "但这也只是道听途说。对于我来讲更直接的意义会是，含频极化率对坐标的一阶导数可以用于计算含频 Raman 光谱。\n",
    "\n",
    "写这篇文档一开始的原因是，曾经在尝试计算简单的 SERS 光谱时，发现 Valley, Schatz et al. [^Valley-Schatz.JPCL.2013] 的含频 Raman 光谱计算的文章提到使用 TD-DFT (time-dependent density functional theory)；其它文献也几乎无一例外。这多少对我来说有点意外。Raman 光谱的计算通过求取极化率对简正坐标的导数 (不管是解析的还是数值的) 得到，而极化率则可以通过 CP-HF (coupled-perturbated Hartree-Fock) 方程给出。此前我确实地成功得到了 Gaussian 所给出的 RKS (GGA level) 极化率，并且并没有使用 TD-DFT 计算，而是 CP-KS (coupled-perturbated Kohn-Sham) 方程计算得到的极化率；我曾经一度以为这是 ADF 软件与 Gaussian 软件两者的区别。后来在各路同学的提醒下，才渐渐明白极化率与含时分析 (TD) 之间的关系。\n",
    "\n",
    "这篇文档将会忽略大部分与公式推导有关的问题；这是由于 TD-DFT 或 TD-HF 的公式推导并不简单；在短时间之内我重复不出让我自己信服的推导。这篇文档不讨论与复数有关的话题，变量与公式全部采用实数与实函数。\n",
    "\n",
    "我们会使用非对称的双氧水分子作为演示分子，基组使用 6-31G。计算程序使用 PySCF 提供电子积分，并与 Gaussian 的含频极化率、PySCF 的激发频率计算结果作对应。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib notebook\n",
    "\n",
    "import numpy as np\n",
    "import scipy\n",
    "from pyscf import gto, scf, tdscf\n",
    "from functools import partial\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib import patches\n",
    "from formchk_interface import FormchkInterface\n",
    "\n",
    "np.einsum = partial(np.einsum, optimize=[\"greedy\", 1024 ** 3 * 2 / 8])\n",
    "np.set_printoptions(5, linewidth=150, suppress=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "全文使用以下记号：\n",
    "\n",
    "- $p, q, r, s, m$ 表示全部轨道\n",
    "\n",
    "- $i, j$ 表示占据分子轨道\n",
    "\n",
    "- $a, b$ 表示非占分子轨道\n",
    "\n",
    "- $\\mu, \\nu, \\kappa, \\lambda$ 表示原子轨道\n",
    "\n",
    "- $t, s$ 在不引起歧义的情况下表示空间取向 $x, y, z$\n",
    "\n",
    "- $P, Q, R, S$ 在这篇文档表示类似于 $ai$ 的组合下标\n",
    "\n",
    "- $n$ 表示 TD-HF 激发态\n",
    "\n",
    "全文使用简化与不严格的 Einstein Summation Convention。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面补充一个原子单位能量 $E_\\mathrm{h}$ 到波数 $\\mathrm{cm}^{-1}$ 的换算 `Eh_cm`：\n",
    "\n",
    "$$\n",
    "1 \\, E_\\mathrm{h} = 219474.6 \\, \\mathrm{cm}^{-1}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "219474.6313702"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from scipy.constants import physical_constants\n",
    "Eh_cm = physical_constants[\"hartree-inverse meter relationship\"][0] / 100\n",
    "Eh_cm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 分子体系与标准结果"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{admonition} 阅读提示\n",
    "\n",
    "我们会花很长的时间进行分子体系与标准结果的定义。如果对代码与文本阅读能力有信心，这段可以跳过。\n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PySCF 体系定义"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在进入下面的讨论前，我们先定义如下变量：\n",
    "\n",
    "- `mol` PySCF 分子实例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<pyscf.gto.mole.Mole at 0x7fc514e22588>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mol = gto.Mole()\n",
    "mol.atom = \"\"\"\n",
    "O  0.0  0.0  0.0\n",
    "O  0.0  0.0  1.5\n",
    "H  1.0  0.0  0.0\n",
    "H  0.0  0.7  1.0\n",
    "\"\"\"\n",
    "mol.basis = \"6-31G\"\n",
    "mol.verbose = 0\n",
    "mol.build()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `nao` 轨道数量 $n_\\mathrm{AO}$, `nocc` 占据轨道数 $n_\\mathrm{occ}$, `nvir` 非占轨道数 $n_\\mathrm{vir}$\n",
    "\n",
    "- `so` 占据轨道分割，`sv` 非占轨道分割，`sa` 全轨道分割\n",
    "\n",
    "- `eri0_ao` 原子轨道基组双电子积分 ERI (electron repulsion integral)\n",
    "\n",
    "    $$\n",
    "    (\\mu \\nu | \\kappa \\lambda) = \\int \\phi_\\mu (\\boldsymbol{r}) \\phi_\\nu (\\boldsymbol{r}) \\frac{1}{|\\boldsymbol{r} - \\boldsymbol{r}'|} \\phi_\\kappa (\\boldsymbol{r}') \\phi_\\lambda (\\boldsymbol{r}') \\, \\mathrm{d} \\boldsymbol{r} \\, \\mathrm{d} \\boldsymbol{r}'\n",
    "    $$\n",
    "\n",
    "- `d_ao` 偶极积分，其中下述的 $t$ 或后文会出现的 $s$ 表示偶极积分的方向 $x, y$ 或 $z$\n",
    "\n",
    "    $$\n",
    "    d_{\\mu \\nu}^t = - \\langle \\mu | t | \\nu \\rangle = \\int \\phi_\\mu (\\boldsymbol{r}) t \\phi_\\nu (\\boldsymbol{r}) \\, \\mathrm{d} \\boldsymbol{r}\n",
    "    $$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "nao = nmo = mol.nao\n",
    "nocc = mol.nelec[0]\n",
    "nvir = nmo - nocc\n",
    "so, sv, sa = slice(0, nocc), slice(nocc, nmo), slice(0, nmo)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "eri0_ao = mol.intor(\"int2e\")\n",
    "d_ao = - mol.intor(\"int1e_r\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `scf_eng` PySCF 的 RHF 计算实例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "scf_eng = scf.RHF(mol).run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `C` $C_{\\mu p}$ 分子轨道系数\n",
    "\n",
    "- `e` $e_p$ RHF 轨道能量\n",
    "\n",
    "- `D` $D_{\\mu \\nu} = 2 C_{\\mu i} C_{\\nu i}$ RHF 电子态密度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "C, e = scf_eng.mo_coeff, scf_eng.mo_energy\n",
    "D = 2 * C[:, so] @ C[:, so].T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `eri0_mo` 分子轨道双电子 ERI $(pq|rs) = C_{\\mu p} C_{\\nu q} (\\mu \\nu | \\kappa \\lambda) C_{\\kappa r} C_{\\lambda s}$\n",
    "\n",
    "- `d_mo` 分子轨道偶极积分 $d^t_{pq} = C_{\\mu p} d^t_{\\mu \\nu} C_{\\nu q}$\n",
    "\n",
    "- `d_ia` 占据-非占的分子轨道偶极积分 $d^t_{ia}$\n",
    "\n",
    "- `d_P` 以双下标 $P = ia$ 为记号的占据-非占分子轨道偶极积分 $d^t_P$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "eri0_mo = np.einsum(\"up, vq, uvkl, kr, ls -> pqrs\", C, C, eri0_ao, C, C)\n",
    "d_mo = np.einsum(\"up, tuv, vq -> tpq\", C, d_ao, C)\n",
    "d_ia = d_mo[:, so, sv]\n",
    "d_P = d_ia.reshape(3, nocc*nvir)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `scf_td` PySCF 的 TD-RHF 计算实例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<pyscf.tdscf.rhf.TDHF at 0x7fc4ddee7048>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scf_td = tdscf.TDHF(scf_eng)\n",
    "scf_td.nstates = nvir * nocc\n",
    "scf_td.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "就我目前所知，PySCF 可以计算极化率；但与含频极化率有关的计算，我在这里采用下文所述的、与 Gaussian 可以大致匹配结果的程序。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Gaussian 计算含频极化率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们需要一个可以核验结果的结果与工具。Gaussian 事实上提供了含频的极化率的选项。这一小段我们简单了解如何使用 Gaussian 来计算含频极化率。\n",
    "\n",
    "我们首先给给出一个示范的例子。这个例子只是演示，并不能代表真实的物理。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Gaussian 的输入卡 {download}`assets/H2O2_freq_polar_example.gjf` 如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "%chk=H2O2_freq_polar_example.chk\n",
      "# RHF/6-31G NoSymm Freq(Raman) CPHF=RdFreq\n",
      "\n",
      "H2O2 Frequency-Dependent Polarizability (Raman)\n",
      "\n",
      "0 1\n",
      "O  0.0  0.0  0.0\n",
      "O  0.0  0.0  1.5\n",
      "H  1.0  0.0  0.0\n",
      "H  0.0  0.7  1.0\n",
      "\n",
      "1nm\n"
     ]
    }
   ],
   "source": [
    "with open(\"assets/H2O2_freq_polar_example.gjf\", \"r\") as f:\n",
    "    print(f.read()[:-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "事实上这段程序不只计算了含频极化率，还计算了含频 Raman；但这份文档只讨论极化率问题。这里的“含频”指的是两个频率，其一为静态 (static) 极化率，其二是入射光线为 $\\omega = 1 \\, \\mathrm{nm}$ 频率下的极化率。后者是一个相当极端的例子，因为通常可能没有人会想到用 X-Ray 照射一个普通的液体分子。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Gaussian 程序默认会输出 .out 或 .log 文件作为文本信息输出，其输出文件在 {download}`assets/H2O2_freq_polar_example.out` 中。我们还要求 Gaussian 输出 .chk 文件，该文件只包含单纯的计算数据信息，其通过 Gaussian Utility `formchk` 导出为 ASCII 格式的文件在 {download}`assets/H2O2_freq_polar_example.fch`。\n",
    "\n",
    "对于 .out 文件，我们可以用下述命令仅查看含频极化率："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " Property number 1 -- Alpha(-w,w) frequency  1    0.000000:\n",
      "                 1             2             3 \n",
      "      1   0.658142D+01 -0.841017D-01 -0.145378D+01\n",
      "      2  -0.841017D-01  0.426836D+01  0.399688D+00\n",
      "      3  -0.145378D+01  0.399688D+00  0.178903D+02\n",
      " Property number 1 -- Alpha(-w,w) frequency  2   45.563353:\n",
      "                 1             2             3 \n",
      "      1  -0.339006D-02  0.323942D-04 -0.262886D-04\n",
      "      2   0.323942D-04 -0.353767D-02  0.379883D-03\n",
      "      3  -0.262886D-04  0.379883D-03 -0.437398D-02\n"
     ]
    }
   ],
   "source": [
    "with open(\"assets/H2O2_freq_polar_example.out\", \"r\") as f:\n",
    "    while f.readable():\n",
    "        line = f.readline()\n",
    "        if \"Alpha(-w,w) frequency\" in line:\n",
    "            print(line[:-1])\n",
    "            for _ in range(4):\n",
    "                print(f.readline()[:-1])\n",
    "        if \"Beta(-w,w,0) frequency\" in line:\n",
    "            break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于每个频率，程序会给出一个 $3 \\times 3$ 大小的矩阵；这就是极化率张量 $\\alpha_{ts} (-\\omega, \\omega)$，其中 $t, s$ 可以是 $x, y, z$。以后的文档中，我们会简记 $\\alpha_{ts} (-\\omega, \\omega)$ 为 $\\alpha_{ts} (\\omega)$。\n",
    "\n",
    "极化率单位是原子单位；关于极化率原子单位与 SI 单位制的转换，参考下述 [NIST 网页](https://www.physics.nist.gov/cgi-bin/cuu/Value?auepol)。\n",
    "\n",
    "上述出现的两个频率值 `0.000000`, `45.563353` 并不是以 $\\mathrm{nm}$ 为单位，而是以 $E_\\mathrm{h}$ Hartree 为单位。关于上述单位换算的过程，我们采用下述代码来说明："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "45.56335252766616"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "1 / Eh_cm * 1e7"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面出现的 `Eh_cm` 已经在文档开头有所解释。这样我们就完成了 .out 文件的含频极化率的读取。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但在后面的文档中，我们将会利用 .chk 文件 (或者几乎等价的 .fch 文件) 的信息，来给出 Gaussian 计算的标准结果。在这份文档中，我们会利用到的键值有两段：`Frequencies for FD properties` 储存了以 $E_\\mathrm{h}$ Hartree 为单位的频率 $\\omega$，`Alpha(-w,w)` 储存了以原子单位储存的极化率张量 $\\alpha_{ts} (\\omega)$。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Frequencies for FD properties              R   N=           2\n",
      "  0.00000000E+00  4.55633525E+01\n",
      "Alpha(-w,w)                                R   N=          18\n",
      "  6.58141820E+00 -8.41017140E-02 -1.45378248E+00 -8.41017140E-02  4.26835620E+00\n",
      "  3.99687823E-01 -1.45378248E+00  3.99687823E-01  1.78903287E+01 -3.39006427E-03\n",
      "  3.23941556E-05 -2.62886421E-05  3.23941556E-05 -3.53766531E-03  3.79883414E-04\n",
      " -2.62886421E-05  3.79883414E-04 -4.37398325E-03\n"
     ]
    }
   ],
   "source": [
    "with open(\"assets/H2O2_freq_polar_example.fch\", \"r\") as f:\n",
    "    print_flag = False\n",
    "    while f.readable():\n",
    "        line = f.readline()\n",
    "        if \"Frequencies for FD properties\" in line:\n",
    "            print_flag = True\n",
    "        if \"Beta(-w,w,0)\" in line:\n",
    "            break\n",
    "        if print_flag is True:\n",
    "            print(line[:-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们可以看到，至少从程序输出的角度来讲，包含频率的极化率很可能与不含极化率的频率值相去甚远。当然，至于在 $1 \\, \\mathrm{nm}$ 如此高的光能量下，这个含频极化率是否在物理上正确，不是这篇文档讨论的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过我们导入的 `FormchkInterface` (取自 [pyxdh 项目](https://github.com/ajz34/Py_xDH/blob/master/pyxdh/Utilities/formchk_interface.py))，我们也可以对上述数值导入到 numpy 向量中。譬如我们需要提取所有频率，那么下面的代码就可以给出："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.     , 45.56335])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fchk_helper = FormchkInterface(\"assets/H2O2_freq_polar_example.fch\")\n",
    "fchk_helper.key_to_value(\"Frequencies for FD properties\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面是 Gaussian 所给出的静态极化率 `ref_alpha_static` $\\alpha_{ts} (-\\omega, \\omega)$。下一段我们会先回顾如何通过 CP-HF 方程，得到静态极化率。下述的矩阵将可以是我们计算结果的参考值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 6.58142, -0.0841 , -1.45378],\n",
       "       [-0.0841 ,  4.26836,  0.39969],\n",
       "       [-1.45378,  0.39969, 17.89033]])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ref_alpha_static = fchk_helper.key_to_value(\"Alpha(-w,w)\")[:9].reshape(3, 3)\n",
    "ref_alpha_static"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "再之后的段落，我们会需要计算含频极化率。Gaussian 一次性至多计算 100 个频率下的光学性质，否则程序会报错；因此我们的输入文件将会是多个文件，这里不列举其超链接。我们用 `freq_all_list` 表示含频极化率对应的频率，而 `alpha_all_list` 表示这些频率下的极化率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "freq_full_list = []\n",
    "alpha_full_list = []\n",
    "for idx in (1, 2, 3):\n",
    "    fchk_helper = FormchkInterface(\"assets/H2O2_freq_polar_{:1d}.fch\".format(idx))\n",
    "    freq_full_list.append(fchk_helper.key_to_value(\"Frequencies for FD properties\")[1:])\n",
    "    alpha_full_list.append(fchk_helper.key_to_value(\"Alpha(-w,w)\").reshape(-1, 3, 3)[1:])\n",
    "freq_full_list = np.concatenate(freq_full_list)\n",
    "alpha_full_list = np.concatenate(alpha_full_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将所获得的含频极化率 (仅绘制其中一个分量 $\\alpha_{zz} (\\omega)$) 绘图可以得到："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "/* Put everything inside the global mpl namespace */\n",
       "window.mpl = {};\n",
       "\n",
       "\n",
       "mpl.get_websocket_type = function() {\n",
       "    if (typeof(WebSocket) !== 'undefined') {\n",
       "        return WebSocket;\n",
       "    } else if (typeof(MozWebSocket) !== 'undefined') {\n",
       "        return MozWebSocket;\n",
       "    } else {\n",
       "        alert('Your browser does not have WebSocket support. ' +\n",
       "              'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
       "              'Firefox 4 and 5 are also supported but you ' +\n",
       "              'have to enable WebSockets in about:config.');\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
       "    this.id = figure_id;\n",
       "\n",
       "    this.ws = websocket;\n",
       "\n",
       "    this.supports_binary = (this.ws.binaryType != undefined);\n",
       "\n",
       "    if (!this.supports_binary) {\n",
       "        var warnings = document.getElementById(\"mpl-warnings\");\n",
       "        if (warnings) {\n",
       "            warnings.style.display = 'block';\n",
       "            warnings.textContent = (\n",
       "                \"This browser does not support binary websocket messages. \" +\n",
       "                    \"Performance may be slow.\");\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.imageObj = new Image();\n",
       "\n",
       "    this.context = undefined;\n",
       "    this.message = undefined;\n",
       "    this.canvas = undefined;\n",
       "    this.rubberband_canvas = undefined;\n",
       "    this.rubberband_context = undefined;\n",
       "    this.format_dropdown = undefined;\n",
       "\n",
       "    this.image_mode = 'full';\n",
       "\n",
       "    this.root = $('<div/>');\n",
       "    this._root_extra_style(this.root)\n",
       "    this.root.attr('style', 'display: inline-block');\n",
       "\n",
       "    $(parent_element).append(this.root);\n",
       "\n",
       "    this._init_header(this);\n",
       "    this._init_canvas(this);\n",
       "    this._init_toolbar(this);\n",
       "\n",
       "    var fig = this;\n",
       "\n",
       "    this.waiting = false;\n",
       "\n",
       "    this.ws.onopen =  function () {\n",
       "            fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
       "            fig.send_message(\"send_image_mode\", {});\n",
       "            if (mpl.ratio != 1) {\n",
       "                fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
       "            }\n",
       "            fig.send_message(\"refresh\", {});\n",
       "        }\n",
       "\n",
       "    this.imageObj.onload = function() {\n",
       "            if (fig.image_mode == 'full') {\n",
       "                // Full images could contain transparency (where diff images\n",
       "                // almost always do), so we need to clear the canvas so that\n",
       "                // there is no ghosting.\n",
       "                fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
       "            }\n",
       "            fig.context.drawImage(fig.imageObj, 0, 0);\n",
       "        };\n",
       "\n",
       "    this.imageObj.onunload = function() {\n",
       "        fig.ws.close();\n",
       "    }\n",
       "\n",
       "    this.ws.onmessage = this._make_on_message_function(this);\n",
       "\n",
       "    this.ondownload = ondownload;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_header = function() {\n",
       "    var titlebar = $(\n",
       "        '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
       "        'ui-helper-clearfix\"/>');\n",
       "    var titletext = $(\n",
       "        '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
       "        'text-align: center; padding: 3px;\"/>');\n",
       "    titlebar.append(titletext)\n",
       "    this.root.append(titlebar);\n",
       "    this.header = titletext[0];\n",
       "}\n",
       "\n",
       "\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_canvas = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var canvas_div = $('<div/>');\n",
       "\n",
       "    canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
       "\n",
       "    function canvas_keyboard_event(event) {\n",
       "        return fig.key_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    canvas_div.keydown('key_press', canvas_keyboard_event);\n",
       "    canvas_div.keyup('key_release', canvas_keyboard_event);\n",
       "    this.canvas_div = canvas_div\n",
       "    this._canvas_extra_style(canvas_div)\n",
       "    this.root.append(canvas_div);\n",
       "\n",
       "    var canvas = $('<canvas/>');\n",
       "    canvas.addClass('mpl-canvas');\n",
       "    canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
       "\n",
       "    this.canvas = canvas[0];\n",
       "    this.context = canvas[0].getContext(\"2d\");\n",
       "\n",
       "    var backingStore = this.context.backingStorePixelRatio ||\n",
       "\tthis.context.webkitBackingStorePixelRatio ||\n",
       "\tthis.context.mozBackingStorePixelRatio ||\n",
       "\tthis.context.msBackingStorePixelRatio ||\n",
       "\tthis.context.oBackingStorePixelRatio ||\n",
       "\tthis.context.backingStorePixelRatio || 1;\n",
       "\n",
       "    mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
       "\n",
       "    var rubberband = $('<canvas/>');\n",
       "    rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
       "\n",
       "    var pass_mouse_events = true;\n",
       "\n",
       "    canvas_div.resizable({\n",
       "        start: function(event, ui) {\n",
       "            pass_mouse_events = false;\n",
       "        },\n",
       "        resize: function(event, ui) {\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "        stop: function(event, ui) {\n",
       "            pass_mouse_events = true;\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "    });\n",
       "\n",
       "    function mouse_event_fn(event) {\n",
       "        if (pass_mouse_events)\n",
       "            return fig.mouse_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    rubberband.mousedown('button_press', mouse_event_fn);\n",
       "    rubberband.mouseup('button_release', mouse_event_fn);\n",
       "    // Throttle sequential mouse events to 1 every 20ms.\n",
       "    rubberband.mousemove('motion_notify', mouse_event_fn);\n",
       "\n",
       "    rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
       "    rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
       "\n",
       "    canvas_div.on(\"wheel\", function (event) {\n",
       "        event = event.originalEvent;\n",
       "        event['data'] = 'scroll'\n",
       "        if (event.deltaY < 0) {\n",
       "            event.step = 1;\n",
       "        } else {\n",
       "            event.step = -1;\n",
       "        }\n",
       "        mouse_event_fn(event);\n",
       "    });\n",
       "\n",
       "    canvas_div.append(canvas);\n",
       "    canvas_div.append(rubberband);\n",
       "\n",
       "    this.rubberband = rubberband;\n",
       "    this.rubberband_canvas = rubberband[0];\n",
       "    this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
       "    this.rubberband_context.strokeStyle = \"#000000\";\n",
       "\n",
       "    this._resize_canvas = function(width, height) {\n",
       "        // Keep the size of the canvas, canvas container, and rubber band\n",
       "        // canvas in synch.\n",
       "        canvas_div.css('width', width)\n",
       "        canvas_div.css('height', height)\n",
       "\n",
       "        canvas.attr('width', width * mpl.ratio);\n",
       "        canvas.attr('height', height * mpl.ratio);\n",
       "        canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
       "\n",
       "        rubberband.attr('width', width);\n",
       "        rubberband.attr('height', height);\n",
       "    }\n",
       "\n",
       "    // Set the figure to an initial 600x600px, this will subsequently be updated\n",
       "    // upon first draw.\n",
       "    this._resize_canvas(600, 600);\n",
       "\n",
       "    // Disable right mouse context menu.\n",
       "    $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
       "        return false;\n",
       "    });\n",
       "\n",
       "    function set_focus () {\n",
       "        canvas.focus();\n",
       "        canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    window.setTimeout(set_focus, 100);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            // put a spacer in here.\n",
       "            continue;\n",
       "        }\n",
       "        var button = $('<button/>');\n",
       "        button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
       "                        'ui-button-icon-only');\n",
       "        button.attr('role', 'button');\n",
       "        button.attr('aria-disabled', 'false');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "\n",
       "        var icon_img = $('<span/>');\n",
       "        icon_img.addClass('ui-button-icon-primary ui-icon');\n",
       "        icon_img.addClass(image);\n",
       "        icon_img.addClass('ui-corner-all');\n",
       "\n",
       "        var tooltip_span = $('<span/>');\n",
       "        tooltip_span.addClass('ui-button-text');\n",
       "        tooltip_span.html(tooltip);\n",
       "\n",
       "        button.append(icon_img);\n",
       "        button.append(tooltip_span);\n",
       "\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    var fmt_picker_span = $('<span/>');\n",
       "\n",
       "    var fmt_picker = $('<select/>');\n",
       "    fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
       "    fmt_picker_span.append(fmt_picker);\n",
       "    nav_element.append(fmt_picker_span);\n",
       "    this.format_dropdown = fmt_picker[0];\n",
       "\n",
       "    for (var ind in mpl.extensions) {\n",
       "        var fmt = mpl.extensions[ind];\n",
       "        var option = $(\n",
       "            '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
       "        fmt_picker.append(option);\n",
       "    }\n",
       "\n",
       "    // Add hover states to the ui-buttons\n",
       "    $( \".ui-button\" ).hover(\n",
       "        function() { $(this).addClass(\"ui-state-hover\");},\n",
       "        function() { $(this).removeClass(\"ui-state-hover\");}\n",
       "    );\n",
       "\n",
       "    var status_bar = $('<span class=\"mpl-message\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
       "    // which will in turn request a refresh of the image.\n",
       "    this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_message = function(type, properties) {\n",
       "    properties['type'] = type;\n",
       "    properties['figure_id'] = this.id;\n",
       "    this.ws.send(JSON.stringify(properties));\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_draw_message = function() {\n",
       "    if (!this.waiting) {\n",
       "        this.waiting = true;\n",
       "        this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
       "    }\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    var format_dropdown = fig.format_dropdown;\n",
       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
       "    fig.ondownload(fig, format);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
       "    var size = msg['size'];\n",
       "    if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
       "        fig._resize_canvas(size[0], size[1]);\n",
       "        fig.send_message(\"refresh\", {});\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
       "    var x0 = msg['x0'] / mpl.ratio;\n",
       "    var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
       "    var x1 = msg['x1'] / mpl.ratio;\n",
       "    var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
       "    x0 = Math.floor(x0) + 0.5;\n",
       "    y0 = Math.floor(y0) + 0.5;\n",
       "    x1 = Math.floor(x1) + 0.5;\n",
       "    y1 = Math.floor(y1) + 0.5;\n",
       "    var min_x = Math.min(x0, x1);\n",
       "    var min_y = Math.min(y0, y1);\n",
       "    var width = Math.abs(x1 - x0);\n",
       "    var height = Math.abs(y1 - y0);\n",
       "\n",
       "    fig.rubberband_context.clearRect(\n",
       "        0, 0, fig.canvas.width, fig.canvas.height);\n",
       "\n",
       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
       "    // Updates the figure title.\n",
       "    fig.header.textContent = msg['label'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
       "    var cursor = msg['cursor'];\n",
       "    switch(cursor)\n",
       "    {\n",
       "    case 0:\n",
       "        cursor = 'pointer';\n",
       "        break;\n",
       "    case 1:\n",
       "        cursor = 'default';\n",
       "        break;\n",
       "    case 2:\n",
       "        cursor = 'crosshair';\n",
       "        break;\n",
       "    case 3:\n",
       "        cursor = 'move';\n",
       "        break;\n",
       "    }\n",
       "    fig.rubberband_canvas.style.cursor = cursor;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
       "    fig.message.textContent = msg['message'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
       "    // Request the server to send over a new figure.\n",
       "    fig.send_draw_message();\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
       "    fig.image_mode = msg['mode'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Called whenever the canvas gets updated.\n",
       "    this.send_message(\"ack\", {});\n",
       "}\n",
       "\n",
       "// A function to construct a web socket function for onmessage handling.\n",
       "// Called in the figure constructor.\n",
       "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
       "    return function socket_on_message(evt) {\n",
       "        if (evt.data instanceof Blob) {\n",
       "            /* FIXME: We get \"Resource interpreted as Image but\n",
       "             * transferred with MIME type text/plain:\" errors on\n",
       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
       "             * to be part of the websocket stream */\n",
       "            evt.data.type = \"image/png\";\n",
       "\n",
       "            /* Free the memory for the previous frames */\n",
       "            if (fig.imageObj.src) {\n",
       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
       "                    fig.imageObj.src);\n",
       "            }\n",
       "\n",
       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
       "                evt.data);\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "        else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
       "            fig.imageObj.src = evt.data;\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        var msg = JSON.parse(evt.data);\n",
       "        var msg_type = msg['type'];\n",
       "\n",
       "        // Call the  \"handle_{type}\" callback, which takes\n",
       "        // the figure and JSON message as its only arguments.\n",
       "        try {\n",
       "            var callback = fig[\"handle_\" + msg_type];\n",
       "        } catch (e) {\n",
       "            console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        if (callback) {\n",
       "            try {\n",
       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
       "                callback(fig, msg);\n",
       "            } catch (e) {\n",
       "                console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
       "            }\n",
       "        }\n",
       "    };\n",
       "}\n",
       "\n",
       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
       "mpl.findpos = function(e) {\n",
       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
       "    var targ;\n",
       "    if (!e)\n",
       "        e = window.event;\n",
       "    if (e.target)\n",
       "        targ = e.target;\n",
       "    else if (e.srcElement)\n",
       "        targ = e.srcElement;\n",
       "    if (targ.nodeType == 3) // defeat Safari bug\n",
       "        targ = targ.parentNode;\n",
       "\n",
       "    // jQuery normalizes the pageX and pageY\n",
       "    // pageX,Y are the mouse positions relative to the document\n",
       "    // offset() returns the position of the element relative to the document\n",
       "    var x = e.pageX - $(targ).offset().left;\n",
       "    var y = e.pageY - $(targ).offset().top;\n",
       "\n",
       "    return {\"x\": x, \"y\": y};\n",
       "};\n",
       "\n",
       "/*\n",
       " * return a copy of an object with only non-object keys\n",
       " * we need this to avoid circular references\n",
       " * http://stackoverflow.com/a/24161582/3208463\n",
       " */\n",
       "function simpleKeys (original) {\n",
       "  return Object.keys(original).reduce(function (obj, key) {\n",
       "    if (typeof original[key] !== 'object')\n",
       "        obj[key] = original[key]\n",
       "    return obj;\n",
       "  }, {});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.mouse_event = function(event, name) {\n",
       "    var canvas_pos = mpl.findpos(event)\n",
       "\n",
       "    if (name === 'button_press')\n",
       "    {\n",
       "        this.canvas.focus();\n",
       "        this.canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    var x = canvas_pos.x * mpl.ratio;\n",
       "    var y = canvas_pos.y * mpl.ratio;\n",
       "\n",
       "    this.send_message(name, {x: x, y: y, button: event.button,\n",
       "                             step: event.step,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "\n",
       "    /* This prevents the web browser from automatically changing to\n",
       "     * the text insertion cursor when the button is pressed.  We want\n",
       "     * to control all of the cursor setting manually through the\n",
       "     * 'cursor' event from matplotlib */\n",
       "    event.preventDefault();\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    // Handle any extra behaviour associated with a key event\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.key_event = function(event, name) {\n",
       "\n",
       "    // Prevent repeat events\n",
       "    if (name == 'key_press')\n",
       "    {\n",
       "        if (event.which === this._key)\n",
       "            return;\n",
       "        else\n",
       "            this._key = event.which;\n",
       "    }\n",
       "    if (name == 'key_release')\n",
       "        this._key = null;\n",
       "\n",
       "    var value = '';\n",
       "    if (event.ctrlKey && event.which != 17)\n",
       "        value += \"ctrl+\";\n",
       "    if (event.altKey && event.which != 18)\n",
       "        value += \"alt+\";\n",
       "    if (event.shiftKey && event.which != 16)\n",
       "        value += \"shift+\";\n",
       "\n",
       "    value += 'k';\n",
       "    value += event.which.toString();\n",
       "\n",
       "    this._key_event_extra(event, name);\n",
       "\n",
       "    this.send_message(name, {key: value,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
       "    if (name == 'download') {\n",
       "        this.handle_save(this, null);\n",
       "    } else {\n",
       "        this.send_message(\"toolbar_button\", {name: name});\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
       "    this.message.textContent = tooltip;\n",
       "};\n",
       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
       "\n",
       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
       "\n",
       "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
       "    // object with the appropriate methods. Currently this is a non binary\n",
       "    // socket, so there is still some room for performance tuning.\n",
       "    var ws = {};\n",
       "\n",
       "    ws.close = function() {\n",
       "        comm.close()\n",
       "    };\n",
       "    ws.send = function(m) {\n",
       "        //console.log('sending', m);\n",
       "        comm.send(m);\n",
       "    };\n",
       "    // Register the callback with on_msg.\n",
       "    comm.on_msg(function(msg) {\n",
       "        //console.log('receiving', msg['content']['data'], msg);\n",
       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
       "        ws.onmessage(msg['content']['data'])\n",
       "    });\n",
       "    return ws;\n",
       "}\n",
       "\n",
       "mpl.mpl_figure_comm = function(comm, msg) {\n",
       "    // This is the function which gets called when the mpl process\n",
       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
       "\n",
       "    var id = msg.content.data.id;\n",
       "    // Get hold of the div created by the display call when the Comm\n",
       "    // socket was opened in Python.\n",
       "    var element = $(\"#\" + id);\n",
       "    var ws_proxy = comm_websocket_adapter(comm)\n",
       "\n",
       "    function ondownload(figure, format) {\n",
       "        window.open(figure.imageObj.src);\n",
       "    }\n",
       "\n",
       "    var fig = new mpl.figure(id, ws_proxy,\n",
       "                           ondownload,\n",
       "                           element.get(0));\n",
       "\n",
       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
       "    // web socket which is closed, not our websocket->open comm proxy.\n",
       "    ws_proxy.onopen();\n",
       "\n",
       "    fig.parent_element = element.get(0);\n",
       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
       "    if (!fig.cell_info) {\n",
       "        console.error(\"Failed to find cell for figure\", id, fig);\n",
       "        return;\n",
       "    }\n",
       "\n",
       "    var output_index = fig.cell_info[2]\n",
       "    var cell = fig.cell_info[0];\n",
       "\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
       "    var width = fig.canvas.width/mpl.ratio\n",
       "    fig.root.unbind('remove')\n",
       "\n",
       "    // Update the output cell to use the data from the current canvas.\n",
       "    fig.push_to_output();\n",
       "    var dataURL = fig.canvas.toDataURL();\n",
       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
       "    // the notebook keyboard shortcuts fail.\n",
       "    IPython.keyboard_manager.enable()\n",
       "    $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
       "    fig.close_ws(fig, msg);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.close_ws = function(fig, msg){\n",
       "    fig.send_message('closing', msg);\n",
       "    // fig.ws.close()\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
       "    // Turn the data on the canvas into data in the output cell.\n",
       "    var width = this.canvas.width/mpl.ratio\n",
       "    var dataURL = this.canvas.toDataURL();\n",
       "    this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Tell IPython that the notebook contents must change.\n",
       "    IPython.notebook.set_dirty(true);\n",
       "    this.send_message(\"ack\", {});\n",
       "    var fig = this;\n",
       "    // Wait a second, then push the new image to the DOM so\n",
       "    // that it is saved nicely (might be nice to debounce this).\n",
       "    setTimeout(function () { fig.push_to_output() }, 1000);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items){\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) { continue; };\n",
       "\n",
       "        var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    // Add the status bar.\n",
       "    var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "\n",
       "    // Add the close button to the window.\n",
       "    var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
       "    var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
       "    button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
       "    button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
       "    buttongrp.append(button);\n",
       "    var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
       "    titlebar.prepend(buttongrp);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(el){\n",
       "    var fig = this\n",
       "    el.on(\"remove\", function(){\n",
       "\tfig.close_ws(fig, {});\n",
       "    });\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(el){\n",
       "    // this is important to make the div 'focusable\n",
       "    el.attr('tabindex', 0)\n",
       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
       "    // off when our div gets focus\n",
       "\n",
       "    // location in version 3\n",
       "    if (IPython.notebook.keyboard_manager) {\n",
       "        IPython.notebook.keyboard_manager.register_events(el);\n",
       "    }\n",
       "    else {\n",
       "        // location in version 2\n",
       "        IPython.keyboard_manager.register_events(el);\n",
       "    }\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    var manager = IPython.notebook.keyboard_manager;\n",
       "    if (!manager)\n",
       "        manager = IPython.keyboard_manager;\n",
       "\n",
       "    // Check for shift+enter\n",
       "    if (event.shiftKey && event.which == 13) {\n",
       "        this.canvas_div.blur();\n",
       "        event.shiftKey = false;\n",
       "        // Send a \"J\" for go to next cell\n",
       "        event.which = 74;\n",
       "        event.keyCode = 74;\n",
       "        manager.command_mode();\n",
       "        manager.handle_keydown(event);\n",
       "    }\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    fig.ondownload(fig, null);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.find_output_cell = function(html_output) {\n",
       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
       "    // IPython event is triggered only after the cells have been serialised, which for\n",
       "    // our purposes (turning an active figure into a static one), is too late.\n",
       "    var cells = IPython.notebook.get_cells();\n",
       "    var ncells = cells.length;\n",
       "    for (var i=0; i<ncells; i++) {\n",
       "        var cell = cells[i];\n",
       "        if (cell.cell_type === 'code'){\n",
       "            for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
       "                var data = cell.output_area.outputs[j];\n",
       "                if (data.data) {\n",
       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
       "                    data = data.data;\n",
       "                }\n",
       "                if (data['text/html'] == html_output) {\n",
       "                    return [cell, data, j];\n",
       "                }\n",
       "            }\n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "// Register the function which deals with the matplotlib target/channel.\n",
       "// The kernel may be null if the page has been refreshed.\n",
       "if (IPython.notebook.kernel != null) {\n",
       "    IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
       "}\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img src=\"\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(freq_full_list, alpha_full_list[:, 2, 2])\n",
    "rect = patches.Rectangle((0.184, -24), 0.01, 78, linewidth=1, edgecolor='C1', facecolor='C1', alpha=.25)\n",
    "ax.add_patch(rect)\n",
    "ax.set_ylim(-25, 75)\n",
    "ax.set_xlabel(r\"$\\omega$ / $E_\\mathrm{h}$\")\n",
    "ax.set_ylabel(r\"$\\alpha_{zz} (\\omega)$ / a.u.\")\n",
    "ax.set_title(\"Frequency-Dependent Polarizability of $\\mathrm{H_2O_2}$ (RHF/6-31G)\")\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "含频极化率的图像在处于分子的激发态区域会呈剧烈振荡。在后续文档中，我们会先更多地看上图中橙色区域表示的前两个激发态。由于上图对橙色部分的描述不很精细，我们下面做一份更精细的极化率图绘制。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "fchk_helper = FormchkInterface(\"assets/H2O2_freq_polar_small_range.fch\")\n",
    "freq_small_list = fchk_helper.key_to_value(\"Frequencies for FD properties\")[1:]\n",
    "alpha_small_list = fchk_helper.key_to_value(\"Alpha(-w,w)\").reshape(-1, 3, 3)[1:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "/* Put everything inside the global mpl namespace */\n",
       "window.mpl = {};\n",
       "\n",
       "\n",
       "mpl.get_websocket_type = function() {\n",
       "    if (typeof(WebSocket) !== 'undefined') {\n",
       "        return WebSocket;\n",
       "    } else if (typeof(MozWebSocket) !== 'undefined') {\n",
       "        return MozWebSocket;\n",
       "    } else {\n",
       "        alert('Your browser does not have WebSocket support. ' +\n",
       "              'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
       "              'Firefox 4 and 5 are also supported but you ' +\n",
       "              'have to enable WebSockets in about:config.');\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
       "    this.id = figure_id;\n",
       "\n",
       "    this.ws = websocket;\n",
       "\n",
       "    this.supports_binary = (this.ws.binaryType != undefined);\n",
       "\n",
       "    if (!this.supports_binary) {\n",
       "        var warnings = document.getElementById(\"mpl-warnings\");\n",
       "        if (warnings) {\n",
       "            warnings.style.display = 'block';\n",
       "            warnings.textContent = (\n",
       "                \"This browser does not support binary websocket messages. \" +\n",
       "                    \"Performance may be slow.\");\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.imageObj = new Image();\n",
       "\n",
       "    this.context = undefined;\n",
       "    this.message = undefined;\n",
       "    this.canvas = undefined;\n",
       "    this.rubberband_canvas = undefined;\n",
       "    this.rubberband_context = undefined;\n",
       "    this.format_dropdown = undefined;\n",
       "\n",
       "    this.image_mode = 'full';\n",
       "\n",
       "    this.root = $('<div/>');\n",
       "    this._root_extra_style(this.root)\n",
       "    this.root.attr('style', 'display: inline-block');\n",
       "\n",
       "    $(parent_element).append(this.root);\n",
       "\n",
       "    this._init_header(this);\n",
       "    this._init_canvas(this);\n",
       "    this._init_toolbar(this);\n",
       "\n",
       "    var fig = this;\n",
       "\n",
       "    this.waiting = false;\n",
       "\n",
       "    this.ws.onopen =  function () {\n",
       "            fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
       "            fig.send_message(\"send_image_mode\", {});\n",
       "            if (mpl.ratio != 1) {\n",
       "                fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
       "            }\n",
       "            fig.send_message(\"refresh\", {});\n",
       "        }\n",
       "\n",
       "    this.imageObj.onload = function() {\n",
       "            if (fig.image_mode == 'full') {\n",
       "                // Full images could contain transparency (where diff images\n",
       "                // almost always do), so we need to clear the canvas so that\n",
       "                // there is no ghosting.\n",
       "                fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
       "            }\n",
       "            fig.context.drawImage(fig.imageObj, 0, 0);\n",
       "        };\n",
       "\n",
       "    this.imageObj.onunload = function() {\n",
       "        fig.ws.close();\n",
       "    }\n",
       "\n",
       "    this.ws.onmessage = this._make_on_message_function(this);\n",
       "\n",
       "    this.ondownload = ondownload;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_header = function() {\n",
       "    var titlebar = $(\n",
       "        '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
       "        'ui-helper-clearfix\"/>');\n",
       "    var titletext = $(\n",
       "        '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
       "        'text-align: center; padding: 3px;\"/>');\n",
       "    titlebar.append(titletext)\n",
       "    this.root.append(titlebar);\n",
       "    this.header = titletext[0];\n",
       "}\n",
       "\n",
       "\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_canvas = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var canvas_div = $('<div/>');\n",
       "\n",
       "    canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
       "\n",
       "    function canvas_keyboard_event(event) {\n",
       "        return fig.key_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    canvas_div.keydown('key_press', canvas_keyboard_event);\n",
       "    canvas_div.keyup('key_release', canvas_keyboard_event);\n",
       "    this.canvas_div = canvas_div\n",
       "    this._canvas_extra_style(canvas_div)\n",
       "    this.root.append(canvas_div);\n",
       "\n",
       "    var canvas = $('<canvas/>');\n",
       "    canvas.addClass('mpl-canvas');\n",
       "    canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
       "\n",
       "    this.canvas = canvas[0];\n",
       "    this.context = canvas[0].getContext(\"2d\");\n",
       "\n",
       "    var backingStore = this.context.backingStorePixelRatio ||\n",
       "\tthis.context.webkitBackingStorePixelRatio ||\n",
       "\tthis.context.mozBackingStorePixelRatio ||\n",
       "\tthis.context.msBackingStorePixelRatio ||\n",
       "\tthis.context.oBackingStorePixelRatio ||\n",
       "\tthis.context.backingStorePixelRatio || 1;\n",
       "\n",
       "    mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
       "\n",
       "    var rubberband = $('<canvas/>');\n",
       "    rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
       "\n",
       "    var pass_mouse_events = true;\n",
       "\n",
       "    canvas_div.resizable({\n",
       "        start: function(event, ui) {\n",
       "            pass_mouse_events = false;\n",
       "        },\n",
       "        resize: function(event, ui) {\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "        stop: function(event, ui) {\n",
       "            pass_mouse_events = true;\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "    });\n",
       "\n",
       "    function mouse_event_fn(event) {\n",
       "        if (pass_mouse_events)\n",
       "            return fig.mouse_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    rubberband.mousedown('button_press', mouse_event_fn);\n",
       "    rubberband.mouseup('button_release', mouse_event_fn);\n",
       "    // Throttle sequential mouse events to 1 every 20ms.\n",
       "    rubberband.mousemove('motion_notify', mouse_event_fn);\n",
       "\n",
       "    rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
       "    rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
       "\n",
       "    canvas_div.on(\"wheel\", function (event) {\n",
       "        event = event.originalEvent;\n",
       "        event['data'] = 'scroll'\n",
       "        if (event.deltaY < 0) {\n",
       "            event.step = 1;\n",
       "        } else {\n",
       "            event.step = -1;\n",
       "        }\n",
       "        mouse_event_fn(event);\n",
       "    });\n",
       "\n",
       "    canvas_div.append(canvas);\n",
       "    canvas_div.append(rubberband);\n",
       "\n",
       "    this.rubberband = rubberband;\n",
       "    this.rubberband_canvas = rubberband[0];\n",
       "    this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
       "    this.rubberband_context.strokeStyle = \"#000000\";\n",
       "\n",
       "    this._resize_canvas = function(width, height) {\n",
       "        // Keep the size of the canvas, canvas container, and rubber band\n",
       "        // canvas in synch.\n",
       "        canvas_div.css('width', width)\n",
       "        canvas_div.css('height', height)\n",
       "\n",
       "        canvas.attr('width', width * mpl.ratio);\n",
       "        canvas.attr('height', height * mpl.ratio);\n",
       "        canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
       "\n",
       "        rubberband.attr('width', width);\n",
       "        rubberband.attr('height', height);\n",
       "    }\n",
       "\n",
       "    // Set the figure to an initial 600x600px, this will subsequently be updated\n",
       "    // upon first draw.\n",
       "    this._resize_canvas(600, 600);\n",
       "\n",
       "    // Disable right mouse context menu.\n",
       "    $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
       "        return false;\n",
       "    });\n",
       "\n",
       "    function set_focus () {\n",
       "        canvas.focus();\n",
       "        canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    window.setTimeout(set_focus, 100);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            // put a spacer in here.\n",
       "            continue;\n",
       "        }\n",
       "        var button = $('<button/>');\n",
       "        button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
       "                        'ui-button-icon-only');\n",
       "        button.attr('role', 'button');\n",
       "        button.attr('aria-disabled', 'false');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "\n",
       "        var icon_img = $('<span/>');\n",
       "        icon_img.addClass('ui-button-icon-primary ui-icon');\n",
       "        icon_img.addClass(image);\n",
       "        icon_img.addClass('ui-corner-all');\n",
       "\n",
       "        var tooltip_span = $('<span/>');\n",
       "        tooltip_span.addClass('ui-button-text');\n",
       "        tooltip_span.html(tooltip);\n",
       "\n",
       "        button.append(icon_img);\n",
       "        button.append(tooltip_span);\n",
       "\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    var fmt_picker_span = $('<span/>');\n",
       "\n",
       "    var fmt_picker = $('<select/>');\n",
       "    fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
       "    fmt_picker_span.append(fmt_picker);\n",
       "    nav_element.append(fmt_picker_span);\n",
       "    this.format_dropdown = fmt_picker[0];\n",
       "\n",
       "    for (var ind in mpl.extensions) {\n",
       "        var fmt = mpl.extensions[ind];\n",
       "        var option = $(\n",
       "            '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
       "        fmt_picker.append(option);\n",
       "    }\n",
       "\n",
       "    // Add hover states to the ui-buttons\n",
       "    $( \".ui-button\" ).hover(\n",
       "        function() { $(this).addClass(\"ui-state-hover\");},\n",
       "        function() { $(this).removeClass(\"ui-state-hover\");}\n",
       "    );\n",
       "\n",
       "    var status_bar = $('<span class=\"mpl-message\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
       "    // which will in turn request a refresh of the image.\n",
       "    this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_message = function(type, properties) {\n",
       "    properties['type'] = type;\n",
       "    properties['figure_id'] = this.id;\n",
       "    this.ws.send(JSON.stringify(properties));\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_draw_message = function() {\n",
       "    if (!this.waiting) {\n",
       "        this.waiting = true;\n",
       "        this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
       "    }\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    var format_dropdown = fig.format_dropdown;\n",
       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
       "    fig.ondownload(fig, format);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
       "    var size = msg['size'];\n",
       "    if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
       "        fig._resize_canvas(size[0], size[1]);\n",
       "        fig.send_message(\"refresh\", {});\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
       "    var x0 = msg['x0'] / mpl.ratio;\n",
       "    var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
       "    var x1 = msg['x1'] / mpl.ratio;\n",
       "    var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
       "    x0 = Math.floor(x0) + 0.5;\n",
       "    y0 = Math.floor(y0) + 0.5;\n",
       "    x1 = Math.floor(x1) + 0.5;\n",
       "    y1 = Math.floor(y1) + 0.5;\n",
       "    var min_x = Math.min(x0, x1);\n",
       "    var min_y = Math.min(y0, y1);\n",
       "    var width = Math.abs(x1 - x0);\n",
       "    var height = Math.abs(y1 - y0);\n",
       "\n",
       "    fig.rubberband_context.clearRect(\n",
       "        0, 0, fig.canvas.width, fig.canvas.height);\n",
       "\n",
       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
       "    // Updates the figure title.\n",
       "    fig.header.textContent = msg['label'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
       "    var cursor = msg['cursor'];\n",
       "    switch(cursor)\n",
       "    {\n",
       "    case 0:\n",
       "        cursor = 'pointer';\n",
       "        break;\n",
       "    case 1:\n",
       "        cursor = 'default';\n",
       "        break;\n",
       "    case 2:\n",
       "        cursor = 'crosshair';\n",
       "        break;\n",
       "    case 3:\n",
       "        cursor = 'move';\n",
       "        break;\n",
       "    }\n",
       "    fig.rubberband_canvas.style.cursor = cursor;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
       "    fig.message.textContent = msg['message'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
       "    // Request the server to send over a new figure.\n",
       "    fig.send_draw_message();\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
       "    fig.image_mode = msg['mode'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Called whenever the canvas gets updated.\n",
       "    this.send_message(\"ack\", {});\n",
       "}\n",
       "\n",
       "// A function to construct a web socket function for onmessage handling.\n",
       "// Called in the figure constructor.\n",
       "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
       "    return function socket_on_message(evt) {\n",
       "        if (evt.data instanceof Blob) {\n",
       "            /* FIXME: We get \"Resource interpreted as Image but\n",
       "             * transferred with MIME type text/plain:\" errors on\n",
       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
       "             * to be part of the websocket stream */\n",
       "            evt.data.type = \"image/png\";\n",
       "\n",
       "            /* Free the memory for the previous frames */\n",
       "            if (fig.imageObj.src) {\n",
       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
       "                    fig.imageObj.src);\n",
       "            }\n",
       "\n",
       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
       "                evt.data);\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "        else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
       "            fig.imageObj.src = evt.data;\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        var msg = JSON.parse(evt.data);\n",
       "        var msg_type = msg['type'];\n",
       "\n",
       "        // Call the  \"handle_{type}\" callback, which takes\n",
       "        // the figure and JSON message as its only arguments.\n",
       "        try {\n",
       "            var callback = fig[\"handle_\" + msg_type];\n",
       "        } catch (e) {\n",
       "            console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        if (callback) {\n",
       "            try {\n",
       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
       "                callback(fig, msg);\n",
       "            } catch (e) {\n",
       "                console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
       "            }\n",
       "        }\n",
       "    };\n",
       "}\n",
       "\n",
       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
       "mpl.findpos = function(e) {\n",
       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
       "    var targ;\n",
       "    if (!e)\n",
       "        e = window.event;\n",
       "    if (e.target)\n",
       "        targ = e.target;\n",
       "    else if (e.srcElement)\n",
       "        targ = e.srcElement;\n",
       "    if (targ.nodeType == 3) // defeat Safari bug\n",
       "        targ = targ.parentNode;\n",
       "\n",
       "    // jQuery normalizes the pageX and pageY\n",
       "    // pageX,Y are the mouse positions relative to the document\n",
       "    // offset() returns the position of the element relative to the document\n",
       "    var x = e.pageX - $(targ).offset().left;\n",
       "    var y = e.pageY - $(targ).offset().top;\n",
       "\n",
       "    return {\"x\": x, \"y\": y};\n",
       "};\n",
       "\n",
       "/*\n",
       " * return a copy of an object with only non-object keys\n",
       " * we need this to avoid circular references\n",
       " * http://stackoverflow.com/a/24161582/3208463\n",
       " */\n",
       "function simpleKeys (original) {\n",
       "  return Object.keys(original).reduce(function (obj, key) {\n",
       "    if (typeof original[key] !== 'object')\n",
       "        obj[key] = original[key]\n",
       "    return obj;\n",
       "  }, {});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.mouse_event = function(event, name) {\n",
       "    var canvas_pos = mpl.findpos(event)\n",
       "\n",
       "    if (name === 'button_press')\n",
       "    {\n",
       "        this.canvas.focus();\n",
       "        this.canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    var x = canvas_pos.x * mpl.ratio;\n",
       "    var y = canvas_pos.y * mpl.ratio;\n",
       "\n",
       "    this.send_message(name, {x: x, y: y, button: event.button,\n",
       "                             step: event.step,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "\n",
       "    /* This prevents the web browser from automatically changing to\n",
       "     * the text insertion cursor when the button is pressed.  We want\n",
       "     * to control all of the cursor setting manually through the\n",
       "     * 'cursor' event from matplotlib */\n",
       "    event.preventDefault();\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    // Handle any extra behaviour associated with a key event\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.key_event = function(event, name) {\n",
       "\n",
       "    // Prevent repeat events\n",
       "    if (name == 'key_press')\n",
       "    {\n",
       "        if (event.which === this._key)\n",
       "            return;\n",
       "        else\n",
       "            this._key = event.which;\n",
       "    }\n",
       "    if (name == 'key_release')\n",
       "        this._key = null;\n",
       "\n",
       "    var value = '';\n",
       "    if (event.ctrlKey && event.which != 17)\n",
       "        value += \"ctrl+\";\n",
       "    if (event.altKey && event.which != 18)\n",
       "        value += \"alt+\";\n",
       "    if (event.shiftKey && event.which != 16)\n",
       "        value += \"shift+\";\n",
       "\n",
       "    value += 'k';\n",
       "    value += event.which.toString();\n",
       "\n",
       "    this._key_event_extra(event, name);\n",
       "\n",
       "    this.send_message(name, {key: value,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
       "    if (name == 'download') {\n",
       "        this.handle_save(this, null);\n",
       "    } else {\n",
       "        this.send_message(\"toolbar_button\", {name: name});\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
       "    this.message.textContent = tooltip;\n",
       "};\n",
       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
       "\n",
       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
       "\n",
       "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
       "    // object with the appropriate methods. Currently this is a non binary\n",
       "    // socket, so there is still some room for performance tuning.\n",
       "    var ws = {};\n",
       "\n",
       "    ws.close = function() {\n",
       "        comm.close()\n",
       "    };\n",
       "    ws.send = function(m) {\n",
       "        //console.log('sending', m);\n",
       "        comm.send(m);\n",
       "    };\n",
       "    // Register the callback with on_msg.\n",
       "    comm.on_msg(function(msg) {\n",
       "        //console.log('receiving', msg['content']['data'], msg);\n",
       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
       "        ws.onmessage(msg['content']['data'])\n",
       "    });\n",
       "    return ws;\n",
       "}\n",
       "\n",
       "mpl.mpl_figure_comm = function(comm, msg) {\n",
       "    // This is the function which gets called when the mpl process\n",
       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
       "\n",
       "    var id = msg.content.data.id;\n",
       "    // Get hold of the div created by the display call when the Comm\n",
       "    // socket was opened in Python.\n",
       "    var element = $(\"#\" + id);\n",
       "    var ws_proxy = comm_websocket_adapter(comm)\n",
       "\n",
       "    function ondownload(figure, format) {\n",
       "        window.open(figure.imageObj.src);\n",
       "    }\n",
       "\n",
       "    var fig = new mpl.figure(id, ws_proxy,\n",
       "                           ondownload,\n",
       "                           element.get(0));\n",
       "\n",
       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
       "    // web socket which is closed, not our websocket->open comm proxy.\n",
       "    ws_proxy.onopen();\n",
       "\n",
       "    fig.parent_element = element.get(0);\n",
       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
       "    if (!fig.cell_info) {\n",
       "        console.error(\"Failed to find cell for figure\", id, fig);\n",
       "        return;\n",
       "    }\n",
       "\n",
       "    var output_index = fig.cell_info[2]\n",
       "    var cell = fig.cell_info[0];\n",
       "\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
       "    var width = fig.canvas.width/mpl.ratio\n",
       "    fig.root.unbind('remove')\n",
       "\n",
       "    // Update the output cell to use the data from the current canvas.\n",
       "    fig.push_to_output();\n",
       "    var dataURL = fig.canvas.toDataURL();\n",
       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
       "    // the notebook keyboard shortcuts fail.\n",
       "    IPython.keyboard_manager.enable()\n",
       "    $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
       "    fig.close_ws(fig, msg);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.close_ws = function(fig, msg){\n",
       "    fig.send_message('closing', msg);\n",
       "    // fig.ws.close()\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
       "    // Turn the data on the canvas into data in the output cell.\n",
       "    var width = this.canvas.width/mpl.ratio\n",
       "    var dataURL = this.canvas.toDataURL();\n",
       "    this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Tell IPython that the notebook contents must change.\n",
       "    IPython.notebook.set_dirty(true);\n",
       "    this.send_message(\"ack\", {});\n",
       "    var fig = this;\n",
       "    // Wait a second, then push the new image to the DOM so\n",
       "    // that it is saved nicely (might be nice to debounce this).\n",
       "    setTimeout(function () { fig.push_to_output() }, 1000);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items){\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) { continue; };\n",
       "\n",
       "        var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    // Add the status bar.\n",
       "    var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "\n",
       "    // Add the close button to the window.\n",
       "    var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
       "    var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
       "    button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
       "    button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
       "    buttongrp.append(button);\n",
       "    var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
       "    titlebar.prepend(buttongrp);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(el){\n",
       "    var fig = this\n",
       "    el.on(\"remove\", function(){\n",
       "\tfig.close_ws(fig, {});\n",
       "    });\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(el){\n",
       "    // this is important to make the div 'focusable\n",
       "    el.attr('tabindex', 0)\n",
       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
       "    // off when our div gets focus\n",
       "\n",
       "    // location in version 3\n",
       "    if (IPython.notebook.keyboard_manager) {\n",
       "        IPython.notebook.keyboard_manager.register_events(el);\n",
       "    }\n",
       "    else {\n",
       "        // location in version 2\n",
       "        IPython.keyboard_manager.register_events(el);\n",
       "    }\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    var manager = IPython.notebook.keyboard_manager;\n",
       "    if (!manager)\n",
       "        manager = IPython.keyboard_manager;\n",
       "\n",
       "    // Check for shift+enter\n",
       "    if (event.shiftKey && event.which == 13) {\n",
       "        this.canvas_div.blur();\n",
       "        event.shiftKey = false;\n",
       "        // Send a \"J\" for go to next cell\n",
       "        event.which = 74;\n",
       "        event.keyCode = 74;\n",
       "        manager.command_mode();\n",
       "        manager.handle_keydown(event);\n",
       "    }\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    fig.ondownload(fig, null);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.find_output_cell = function(html_output) {\n",
       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
       "    // IPython event is triggered only after the cells have been serialised, which for\n",
       "    // our purposes (turning an active figure into a static one), is too late.\n",
       "    var cells = IPython.notebook.get_cells();\n",
       "    var ncells = cells.length;\n",
       "    for (var i=0; i<ncells; i++) {\n",
       "        var cell = cells[i];\n",
       "        if (cell.cell_type === 'code'){\n",
       "            for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
       "                var data = cell.output_area.outputs[j];\n",
       "                if (data.data) {\n",
       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
       "                    data = data.data;\n",
       "                }\n",
       "                if (data['text/html'] == html_output) {\n",
       "                    return [cell, data, j];\n",
       "                }\n",
       "            }\n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "// Register the function which deals with the matplotlib target/channel.\n",
       "// The kernel may be null if the page has been refreshed.\n",
       "if (IPython.notebook.kernel != null) {\n",
       "    IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
       "}\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img src=\"\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(freq_small_list, alpha_small_list[:, 2, 2])\n",
    "ax.set_xlabel(r\"$\\omega$ / $E_\\mathrm{h}$\")\n",
    "ax.set_ylabel(r\"$\\alpha_{zz} (\\omega)$ / a.u.\")\n",
    "ax.set_title(\"Frequency-Dependent Polarizability of $\\mathrm{H_2O_2}$ (RHF/6-31G)\\nFor First Two Excited States\")\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从这两张图的纵坐标的缩放关系来看，事实上，对于每一个振荡峰，其振荡是趋于无穷大的；并且其对应的频率恰好是分子 TD-HF 计算得到的激发能。这将会在后面的文档中叙述并验证。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TD-HF 方程过程回顾"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TD-HF 方程与激发能"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一般会认为，TD 方法是用于求解与电子激发过程有关的方法。最常用的应用即是求解激发能与跃迁偶极矩。我们在这一段先回顾这两者的计算过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在进行后续的描述前，我们会定义下述与 TD-HF 方程有关的张量或矩阵 `A` $\\mathbb{A}_{ia, jb}$ 与 `B` $\\mathbb{B}_{ia, jb}$：\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\mathbb{A}_{ia, jb} &= (\\varepsilon_a - \\varepsilon_i) \\delta_{ij} \\delta_{ab} + 2 (ia|jb) - (ij|ab) \\\\\n",
    "\\mathbb{B}_{ia, jb} &= 2 (ia|jb) - (ib|ja)\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "其中两个辅助变量为：\n",
    "\n",
    "- `delta_ij` $\\delta_{ij}$ 为占据轨道数维度的单位矩阵\n",
    "\n",
    "- `delta_ab` $\\delta_{ab}$ 为非占轨道数维度的单位矩阵"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "delta_ij, delta_ab = np.eye(nocc), np.eye(nvir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "A_iajb = (\n",
    "    np.einsum(\"ia, ij, ab -> iajb\", - e[so, None] + e[sv], delta_ij, delta_ab)\n",
    "    + 2 * eri0_mo[so, sv, so, sv]\n",
    "    - eri0_mo[so, so, sv, sv].swapaxes(1, 2))\n",
    "B_iajb = (\n",
    "    + 2 * eri0_mo[so, sv, so, sv]\n",
    "    - eri0_mo[so, sv, so, sv].swapaxes(1, 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了后文的代码方便，我们把双下标的矩阵记为 `A` $A_{PQ}$ 与 `B` $B_{PQ}$："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = A_iajb.reshape(nocc*nvir, nocc*nvir)\n",
    "B = B_iajb.reshape(nocc*nvir, nocc*nvir)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据 TD-DFT 中的 Casida 方程 (TD-DFT 可以看作是 TD-HF 情形的扩展)，我们可以写出 TD-HF 的频率及其对应的本征向量为 $\\omega_n, X_{ia}^n, Y_{ia}^n$，或者双下标记号的 $X_{P}^n, Y_{P}^n$。其中，$X_{ia}^n$ 有时称为第 $n$ 个激发态的激发矩阵，$Y_{ia}^n$ 则称退激发矩阵。这几者之间满足下述 TD-HF 矩阵方程。\n",
    "\n",
    "$$\n",
    "\\begin{pmatrix} \\mathbb{A} & \\mathbb{B} \\\\ - \\mathbb{B} & - \\mathbb{A} \\end{pmatrix}\n",
    "\\begin{pmatrix} \\mathbf{X}^n \\\\ \\mathbf{Y}^n \\end{pmatrix}\n",
    "= \\omega_n \\begin{pmatrix} \\mathbf{X}^n \\\\ \\mathbf{Y}^n \\end{pmatrix}\n",
    "$$\n",
    "\n",
    "我们在程序上，记等式左边的大矩阵为 `AB`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(234, 234)"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AB = np.block([\n",
    "    [  A,   B],\n",
    "    [- B, - A]\n",
    "])\n",
    "AB.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们首先解出上述矩阵的本征值 `eigs` 与本征向量 `xys`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "eigs, xys = np.linalg.eig(AB)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但我们会发现，我们本来预期的在 6-31G 基组下可解的激发态数量只有 $n_\\mathrm{occ} n_\\mathrm{vir} = 117$，但本征值数量却是 234 个。我们需要舍去所有负值的本征值。事实上，负值的本征值与正值的本征值之间有一一对应的关系。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "117"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(eigs < 0).sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们舍去负本征值及其对应的本征向量，并对本征值作排序，得到正的本征值 `eigs_sorted` 及其相对应的本征向量 `xys_sorted`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "eigs_sorted = eigs[eigs.argsort()[int(eigs.size / 2):]]\n",
    "xys_sorted = xys[:, eigs.argsort()[int(eigs.size / 2):]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们应当可以验证，上述本征值与本征向量确实满足 TD-HF 矩阵方程："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(AB @ xys_sorted, eigs_sorted * xys_sorted)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最后，我们用 `td_eig` $\\omega_n$、`td_x_unnormed` 未归一化的 $X^n_P$、`td_y_unnormed` 未归一化的 $Y^n_P$ 来重新整理上述的结果 `eigs_sorted` 与 `xys_sorted`。需要注意，变量 `td_x_unnormed` 的两个维度中，第一个维度为激发态 $n$，第二个维度为双下标 $P = ia$；尽管两个维度的大小都是 $n_\\mathrm{occ} n_\\mathrm{vir} = 117$，但意义完全不同。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "td_eig = eigs_sorted\n",
    "td_x_unnormed = xys_sorted.T[:, :nvir*nocc]\n",
    "td_y_unnormed = xys_sorted.T[:, nvir*nocc:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们简单看一下最低激发能的几个激发态的能级大小，单位是原子单位或 Hartree $E_\\mathrm{h}$："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.18674, 0.19114, 0.35357, 0.39384, 0.41744, 0.42516, 0.45701, 0.4702 , 0.50732, 0.55833])"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eigs_sorted[:10]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们能看到最低的激发态中，有 0.187 与 0.191；这恰好与上面 Gaussian 绘制出来的含频极化率图中的两个振荡峰位置恰好吻合。这并非是偶然，并且我们会在后文进行更详细的描述。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TD-HF 跃迁偶极矩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从基态波函数 $| 0 \\rangle$ 到激发态波函数 $| n \\rangle$ 的跃迁偶极矩可以写作 $\\langle 0 | \\hat d{}^t | n \\rangle$ 或等价的 $- \\langle 0 | t | n \\rangle$；留意到 $t \\in \\{ x, y, z \\}$。但实施上，我们尚不能写出激发态波函数 $| n \\rangle$ 的具体形式。这个激发态波函数需要通过激发矩阵 $X_{ia}^n$ 与退激发矩阵 $Y_{ia}^n$ 来描述。\n",
    "\n",
    "刚才的计算中，我们得到的本征向量是未经归一化的；它乘以任何非零常数，仍然会是 TD-HF 矩阵方程的本征向量。但我们可以使用激发与退激发，赋予这个本征向量以物理含义。其归一化条件是，态 $| n \\rangle$ 的电子数守恒，即与 $| 0 \\rangle$ 的电子数相同。在 RHF 问题下，这要求\n",
    "\n",
    "$$\n",
    "(X_{ia}^n)^2 - (Y_{ia}^n)^2 = 2\n",
    "$$\n",
    "\n",
    "我们令归一化过程中的中间量为 `td_renorm` $N_n = \\frac{1}{2} \\left( (X_{ia}^n)^2 - (Y_{ia}^n)^2 \\right)$："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "td_renorm = ((td_x_unnormed**2).sum(axis=1) - (td_y_unnormed**2).sum(axis=1)) / 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么重新归一化后的 `X` $X_P^n$ 与 `Y` $Y_P^n$ 为"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = td_x_unnormed / np.sqrt(td_renorm)[:, None]\n",
    "Y = td_y_unnormed / np.sqrt(td_renorm)[:, None]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了处理一些问题的便利，我们声明变量 `X_ia` $X_{ia}^n$ 与 `Y_ia` $Y_{ia}^n$；它们的维度均是 $(n, i, a)$："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(117, 9, 13)"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_ia = X.reshape(nocc*nvir, nocc, nvir)\n",
    "Y_ia = Y.reshape(nocc*nvir, nocc, nvir)\n",
    "X_ia.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以此为基础，我们可以写出 TD-HF 的跃迁偶极矩 `td_transdip`\n",
    "\n",
    "$$\n",
    "\\langle 0 | \\hat d{}^t | n \\rangle = d_{ia}^t (X_{ia}^n + Y_{ia}^n)\n",
    "$$\n",
    "\n",
    "我们会打印出最低能级的 5 个激发态的跃迁偶极矩："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.00096, -0.01194,  0.10696],\n",
       "       [-0.01912, -0.02168,  0.07287],\n",
       "       [ 0.01189,  0.06348, -0.09044],\n",
       "       [-0.28032,  0.11958,  1.16236],\n",
       "       [-0.11797,  0.0042 , -0.50674]])"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "td_transdip = np.einsum(\"tia, nia -> nt\", d_ia, X_ia + Y_ia)\n",
    "td_transdip[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这与 PySCF 所给出的跃迁偶极矩几乎是相同的，但符号上会有差异。我们认为这已经完整并成功地重复了跃迁偶极矩了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.00096, -0.01194,  0.10696],\n",
       "       [ 0.01912,  0.02168, -0.07287],\n",
       "       [ 0.01189,  0.06348, -0.09044],\n",
       "       [ 0.28032, -0.11958, -1.16236],\n",
       "       [-0.11797,  0.0042 , -0.50674]])"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scf_td.transition_dipole()[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "需要注意，这可能与 Gaussian 计算得到的跃迁偶极矩的值接近但并不完全相等。这可能与 Gaussian 默认的 TD-HF 精度偏低有关。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 静态极化率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 偶极微扰下的 CP-HF 方程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这篇文档的一个目的是将 CP-HF 方程与 TD-HF 方程之间的关系作一个联系。因此，我们需要首先了解 CP-HF 方程在静态极化率中的工作过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{attention}\n",
    "\n",
    "尽管我们确实可以用后续文档中的代码或公式计算得到一些结果，但这并不意味着成型的量化软件也使用这些算法。譬如对于 RHF 下静态极化率计算，通常更高效的做法是使用类似于 CP-HF 方程的 Z-Vector 方程。\n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由此，我们会写偶极微扰下的 CP-HF 方程为\n",
    "\n",
    "$$\n",
    "A'_{ia, jb} U^t_{jb} = d^t_{ia}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面简单但不严谨地回顾 CP-HF 方程的推导思路。我们在分子体系上，外加一个微扰偶极场，其大小是分子轨道基组下的 $d_{pq}^t$ 偶极矩阵，微扰哈密顿量为 $t$ (即单位方向为 $t$ 的电场微扰)。根据 RHF 的变分条件，任何外加微扰的哈密顿量 $t$ 都应该满足\n",
    "\n",
    "$$\n",
    "\\frac{\\partial F_{pq}}{\\partial t} = 0\n",
    "$$\n",
    "\n",
    "通过该式，几乎可以直接得到 CP-HF 方程。方程的左边定义上是偶极积分，右边的 `A_p` $A'_{ia, jb}$ 为\n",
    "\n",
    "$$\n",
    "A'_{ia, jb} = (\\varepsilon_a - \\varepsilon_i) \\delta_{ij} \\delta_{ab} + 4 (ia|jb) - (ij|ab) - (ib|ja)\n",
    "$$\n",
    "\n",
    "而 `U_ia` $U_{jb}^t$ 称为 U 矩阵，表示的是与电子态密度在外加偶极微扰影响下的变化有关的量；一种导出式如下：\n",
    "\n",
    "$$\n",
    "\\frac{\\partial D_{pq}}{\\partial t} = D_{pm} U^t_{mq} + D_{mq} U^t_{mp}\n",
    "$$\n",
    "\n",
    "因此，CP-HF 的一种直观的解释思路是，它求取的是分子受到外加偶极的微扰 $d^t_{ia}$ 下，电子态密度形变的大小，而这个大小是由 $U_{jb}^t$ U 矩阵刻画的。很容易想到的性质是，若外加偶极微扰趋于零，那么外加的形变微扰也趋于零矩阵。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们来求取 CP-HF 方程，给出 `A_p` $A'_{PQ} = A'_{ia, jb}$ 与 `U_ia` $U_{jb}^t$。需要注意在这份文档中，角标顺序是 $ia, jb$ 而非 $ai, bj$；这可能与其它课本或文档的顺序不太相同，在一些矩阵的正负号上也可能存在差异。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "A_p = (\n",
    "    + np.einsum(\"ia, ij, ab -> iajb\", - e[so, None] + e[sv], delta_ij, delta_ab)\n",
    "    + 4 * eri0_mo[so, sv, so, sv]\n",
    "    - eri0_mo[so, so, sv, sv].swapaxes(1, 2)\n",
    "    - eri0_mo[so, sv, so, sv].swapaxes(1, 3)\n",
    ").reshape(nvir*nocc, nvir*nocc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "U_ia = np.einsum(\"PQ, sQ -> sP\", np.linalg.inv(A_p), d_P)\n",
    "U_ia.shape = (3, nocc, nvir)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "随后，根据求导法则与矩阵的对称性、反对称性的应用，应当可以得到静态极化率表达式为\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = \\frac{\\partial^2 E_\\mathrm{RHF}}{\\partial t \\partial s} = \\frac{\\partial D_{ij} d^t_{ij} \\delta_{ij}}{\\partial s} = 4 d^t_{ia} U^s_{ia}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 6.58142, -0.0841 , -1.45378],\n",
       "       [-0.0841 ,  4.26835,  0.39969],\n",
       "       [-1.45378,  0.39969, 17.89033]])"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "4 * np.einsum(\"tia, sia -> ts\", d_ia, U_ia)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上述的结果与 Gaussian 计算所得到的静态极化率 `ref_alpha_static` 完全一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(\n",
    "    4 * np.einsum(\"tia, sia -> ts\", d_ia, U_ia),\n",
    "    ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 矩阵求逆直接获得静态极化率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据我们所写的 CP-HF 方程\n",
    "\n",
    "$$\n",
    "A'_{ia, jb} U^t_{jb} = d^t_{ia}\n",
    "$$\n",
    "\n",
    "我们应当很容易地想到，如果我们有足够的计算能力，可以对四脚标矩阵 $A'_{ia, jb}$ 求逆，那么我们不一定需要明确写出 U 矩阵，也一样可以求得静态极化率：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 4 d^t_{ia} (A'{}^{-1})_{ia, jb} d^s_{ia}\n",
    "$$\n",
    "\n",
    "当然，上面的计算过程实际上是用双下标 ($P = ia$, $Q = jb$) 表达式实现的：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 4 d^t_{P} (A'{}^{-1})_{PQ} d^s_{Q}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(4 * np.einsum(\"tP, PQ, sQ -> ts\", d_P, np.linalg.inv(A_p), d_P), ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 跃迁偶极矩获得静态极化率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "CP-HF 方程求解静态极化率的思路是非常直观的；但静态极化率还可以通过 TD-HF 的方式求得。表面上，这两种推导思路和前提几乎完全不同；但我们却可以得到数值上 **完全** (而非近似) 相等的静态极化率。这里我们会作说明。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们首先不加说明地直接给出 TD-HF 方式给出的静态极化率公式：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 2 \\frac{\\langle 0 | \\hat d{}^t | n \\rangle \\langle n | \\hat d{}^s | 0 \\rangle}{\\omega_n}\n",
    "$$\n",
    "\n",
    "它很容易化为程序表达式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 6.58142, -0.0841 , -1.45378],\n",
       "       [-0.0841 ,  4.26835,  0.39969],\n",
       "       [-1.45378,  0.39969, 17.89033]])"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "2 * np.einsum(\"nt, n, ns -> ts\", td_transdip, 1 / td_eig, td_transdip)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们与上面用 CP-HF 方式计算得到的静态极化率作对比，不难发现两者的值是完全相等的。可以用下述程序与 Gaussian 的计算结果作对比："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(2 * np.einsum(\"nt, n, ns -> ts\", td_transdip, 1 / td_eig, td_transdip), ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这说明，对于静态极化率问题，TD-HF 与 CP-HF 方法之间有着确实的联系。我们下面就使用 TD-HF 方程来导出 CP-HF 方程的结果，或者说从简单的线性代数角度证明：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 2 \\frac{\\langle 0 | \\hat d{}^t | n \\rangle \\langle n | \\hat d{}^s | 0 \\rangle}{\\omega_n} = 4 d^t_{P} (A'{}^{-1})_{PQ} d^s_{Q}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 静态极化率下 TD-HF 方程与 CP-HF 方程的等价推导"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里我们会给出静态情况下，TD-HF 与 CP-HF 方程的推演过程。对于动态 (含频) 过程的推演，我们会放在文档的后面描述。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首先，我们会说明 TD-HF 方程的 `A` $\\mathbb{A}_{PQ}$ 与 `B` $\\mathbb{B}_{PQ}$ 之和，恰好是 CP-HF 方程的 `A_p` $A'_{PQ}$：\n",
    "\n",
    "$$\n",
    "A'_{PQ} = \\mathbb{A}_{PQ} + \\mathbb{B}_{PQ}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(A + B, A_p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "利用 $\\langle 0 | \\hat d{}^t | n \\rangle = d_{ia}^t (X_{ia}^n + Y_{ia}^n)$，我们重新用 $X_P^n, Y_P^n$ 的形式写一下 TD-HF 方程所给出的极化率公式：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 2 \\frac{d_P^t d_Q^s (X_P^n + Y_P^n) (X_Q^n + Y_Q^n)}{\\omega_n}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(2 * np.einsum(\"tP, sQ, nP, nQ, n -> ts\", d_P, d_P, X + Y, X + Y, 1 / td_eig), ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "回顾到 TD-HF 方程\n",
    "\n",
    "$$\n",
    "\\begin{pmatrix} \\mathbb{A} & \\mathbb{B} \\\\ - \\mathbb{B} & - \\mathbb{A} \\end{pmatrix}\n",
    "\\begin{pmatrix} \\mathbf{X}^n \\\\ \\mathbf{Y}^n \\end{pmatrix}\n",
    "= \\omega_n \\begin{pmatrix} \\mathbf{X}^n \\\\ \\mathbf{Y}^n \\end{pmatrix}\n",
    "$$\n",
    "\n",
    "我们可以推知，$(\\mathbb{A} + \\mathbb{B}) (\\mathbf{X}^n + \\mathbf{Y}^n) = \\omega_n (\\mathbf{X}^n - \\mathbf{Y}^n)$，或者写为角标求和的形式，\n",
    "\n",
    "$$\n",
    "(\\mathbb{A} + \\mathbb{B})_{RQ} (\\mathbf{X}^n + \\mathbf{Y}^n)_Q = \\omega_n (\\mathbf{X}^n - \\mathbf{Y}^n)_R\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么我们可以将上式，以及矩阵逆关系 $(\\mathbb{A} + \\mathbb{B})^{-1} (\\mathbb{A} + \\mathbb{B}) = \\mathbb{1}$ 即\n",
    "\n",
    "$$\n",
    "(\\mathbb{A} + \\mathbb{B})^{-1}_{SR} (\\mathbb{A} + \\mathbb{B})_{RQ} = \\delta_{SQ}\n",
    "$$\n",
    "\n",
    "代入到上面提到的 TD-HF 的极化率公式中，得到\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\alpha_{ts} (0)\n",
    "&= 2 \\frac{d_P^t d_Q^s (X_P^n + Y_P^n) (\\mathbb{A} + \\mathbb{B})^{-1}_{QR} (\\mathbb{A} + \\mathbb{B})_{RQ} (X_Q^n + Y_Q^n)}{\\omega_n} \\\\\n",
    "&= 2 d_P^t d_Q^s (X_P^n + Y_P^n) (\\mathbb{A} + \\mathbb{B})^{-1}_{QR} (X_R^n - Y_R^n)\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "随后我们需要利用 $\\mathbf{X}^n, \\mathbf{Y}^n$ 的正交化条件。正交化条件我们曾经在给出 $\\mathbf{X}^n, \\mathbf{Y}^n$ 时确实利用到过，但其更有用的推论是 $(\\mathbf{X} + \\mathbf{Y})^\\dagger (\\mathbf{X} - \\mathbf{Y}) = 2 \\cdot \\mathbb{1}$\n",
    "\n",
    "$$\n",
    "(\\mathbf{X}^n + \\mathbf{Y}^n)_P (\\mathbf{X}^n - \\mathbf{Y}^n)_R = 2 \\delta_{PR}\n",
    "$$\n",
    "\n",
    "我们可以用下面的程序来说明这一问题："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2., -0.,  0., ...,  0.,  0., -0.],\n",
       "       [ 0.,  2.,  0., ...,  0.,  0.,  0.],\n",
       "       [ 0.,  0.,  2., ...,  0., -0.,  0.],\n",
       "       ...,\n",
       "       [ 0.,  0., -0., ...,  2., -0., -0.],\n",
       "       [ 0.,  0.,  0., ..., -0.,  2., -0.],\n",
       "       [-0.,  0., -0., ..., -0., -0.,  2.]])"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.einsum(\"nP, nQ -> PQ\", X + Y, X - Y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么我们就可以将上面的极化率公式化为\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\alpha_{ts} (0)\n",
    "&= 2 d_P^t d_Q^s (\\mathbb{A} + \\mathbb{B})^{-1}_{QR} \\cdot 2 \\delta_{PR} \\\\\n",
    "&= 4 d_Q^s (\\mathbb{A} + \\mathbb{B})^{-1}_{QP} d_P^t \\\\\n",
    "&= 4 d_Q^s (A'{}^{-1})_{QP} d^t_P\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们知道，上式的等式左边是对 $Q, P$ 双下标进行求和。作为被求和的两个下标是可以被交换的，因此我们可以将上式写为\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 4 d_P^s (A'{}^{-1})_{PQ} d^t_Q\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(4 * np.einsum(\"sP, PQ, tQ -> ts\", d_P, np.linalg.inv(A_p), d_P), ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上述推导并没有结束。我们回顾到刚才 CP-HF 所给出的极化率并不是上述的表达式，而是交换了 $t, s$ 两者的极化率：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (0) = 4 d_P^t (A'{}^{-1})_{PQ} d^s_Q\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(4 * np.einsum(\"tP, PQ, sQ -> ts\", d_P, np.linalg.inv(A_p), d_P), ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从极化率作为能量的二阶梯度的角度来说，这是因为被求导量可交换，因此极化率具有 Hermite 性质：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega) = \\alpha_{st} (\\omega)\n",
    "$$\n",
    "\n",
    "到这一步为止，从 TD-HF 方程给出的极化率，成功地推导出了 CP-HF 方程所给出的极化率。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 含频极化率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 跃迁偶极矩获得含频极化率"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们先不对含频极化率作公式上的分析，先只看数值的结果，并与 Gaussian 输出的结果进行比较。\n",
    "\n",
    "我们仍然不加说明地直接给出 TD-HF 方式给出的含频极化率公式：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega_n) = \\frac{\\langle 0 | \\hat d{}^t | n \\rangle \\langle n | \\hat d{}^s | 0 \\rangle}{\\omega_n - \\omega} + \\frac{\\langle 0 | \\hat d{}^t | n \\rangle \\langle n | \\hat d{}^s | 0 \\rangle}{\\omega_n + \\omega}\n",
    "$$\n",
    "\n",
    "需要留意的是，$\\omega_n$ 为分子通过 TD-HF 方程解出来的激发能，而 $\\omega$ 是外加的、任意的激发光束频率；两者除了单位一致外几乎完全无关。\n",
    "\n",
    "上述公式中，前一项称为共振项 (resonance term)，后一项称为非共振项。这两项在不同频率下的行为可以很容易地用图片表示出来；当 $\\omega$ 接近激发频率 $\\omega_n$ 时产生断点行为的项是共振项。\n",
    "\n",
    "它也很容易化为程序表达式。我们用下述函数 `freq_to_alpha`，输入 `omega` $\\omega$ 来返回含频频率 $\\alpha_{ts} (\\omega_n)$；并将其中的共振项与非共振项拆分为函数 `freq_to_res` 与 `freq_to_nonres`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "freq_to_res    = lambda omega: np.einsum(\"nt, n, ns -> ts\", td_transdip, 1 / (td_eig - omega), td_transdip)\n",
    "freq_to_nonres = lambda omega: np.einsum(\"nt, n, ns -> ts\", td_transdip, 1 / (td_eig + omega), td_transdip)\n",
    "freq_to_alpha  = lambda omega: freq_to_res(omega) + freq_to_nonres(omega)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若外加激发光束频率为 0，那么将退化到静态极化率的情形中："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 6.58142, -0.0841 , -1.45378],\n",
       "       [-0.0841 ,  4.26835,  0.39969],\n",
       "       [-1.45378,  0.39969, 17.89033]])"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "freq_to_alpha(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(freq_to_alpha(0), ref_alpha_static)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在静态情况下，共振项与非共振项对总极化率的贡献是相等的："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 3.29071, -0.04205, -0.72689],\n",
       "       [-0.04205,  2.13418,  0.19984],\n",
       "       [-0.72689,  0.19984,  8.94517]])"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "freq_to_res(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们现在想要尝试与 Gaussian 的数据进行核对并绘制图片。回顾到我们曾经定义过 `freq_full_list` 为较广的频率范围，其对应的 Gaussian 计算的极化率在 `alpha_full_list`。我们将用 `freq_to_alpha` 函数绘制的 $\\alpha_{zz} (\\omega)$ 极化率分量放在 numpy 张量列表 `alpha_zz_full_calc` 中；并将其共振项与非共振项分别放在 `alpha_zz_full_res` 与 `alpha_zz_full_nonres`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "alpha_zz_full_calc   = np.vectorize(lambda omega: freq_to_alpha (omega)[2, 2])(freq_full_list)\n",
    "alpha_zz_full_res    = np.vectorize(lambda omega: freq_to_res   (omega)[2, 2])(freq_full_list)\n",
    "alpha_zz_full_nonres = np.vectorize(lambda omega: freq_to_nonres(omega)[2, 2])(freq_full_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们绘制在这个频率区间内，Gaussian 计算得到的结果与我们用上面跃迁偶极矩的公式获得的结果作比较。尽管在断点附近两者表现略有不同，但稳定区间的含频极化率与 Gaussian 的结果基本上是一致的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "/* Put everything inside the global mpl namespace */\n",
       "window.mpl = {};\n",
       "\n",
       "\n",
       "mpl.get_websocket_type = function() {\n",
       "    if (typeof(WebSocket) !== 'undefined') {\n",
       "        return WebSocket;\n",
       "    } else if (typeof(MozWebSocket) !== 'undefined') {\n",
       "        return MozWebSocket;\n",
       "    } else {\n",
       "        alert('Your browser does not have WebSocket support. ' +\n",
       "              'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
       "              'Firefox 4 and 5 are also supported but you ' +\n",
       "              'have to enable WebSockets in about:config.');\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
       "    this.id = figure_id;\n",
       "\n",
       "    this.ws = websocket;\n",
       "\n",
       "    this.supports_binary = (this.ws.binaryType != undefined);\n",
       "\n",
       "    if (!this.supports_binary) {\n",
       "        var warnings = document.getElementById(\"mpl-warnings\");\n",
       "        if (warnings) {\n",
       "            warnings.style.display = 'block';\n",
       "            warnings.textContent = (\n",
       "                \"This browser does not support binary websocket messages. \" +\n",
       "                    \"Performance may be slow.\");\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.imageObj = new Image();\n",
       "\n",
       "    this.context = undefined;\n",
       "    this.message = undefined;\n",
       "    this.canvas = undefined;\n",
       "    this.rubberband_canvas = undefined;\n",
       "    this.rubberband_context = undefined;\n",
       "    this.format_dropdown = undefined;\n",
       "\n",
       "    this.image_mode = 'full';\n",
       "\n",
       "    this.root = $('<div/>');\n",
       "    this._root_extra_style(this.root)\n",
       "    this.root.attr('style', 'display: inline-block');\n",
       "\n",
       "    $(parent_element).append(this.root);\n",
       "\n",
       "    this._init_header(this);\n",
       "    this._init_canvas(this);\n",
       "    this._init_toolbar(this);\n",
       "\n",
       "    var fig = this;\n",
       "\n",
       "    this.waiting = false;\n",
       "\n",
       "    this.ws.onopen =  function () {\n",
       "            fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
       "            fig.send_message(\"send_image_mode\", {});\n",
       "            if (mpl.ratio != 1) {\n",
       "                fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
       "            }\n",
       "            fig.send_message(\"refresh\", {});\n",
       "        }\n",
       "\n",
       "    this.imageObj.onload = function() {\n",
       "            if (fig.image_mode == 'full') {\n",
       "                // Full images could contain transparency (where diff images\n",
       "                // almost always do), so we need to clear the canvas so that\n",
       "                // there is no ghosting.\n",
       "                fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
       "            }\n",
       "            fig.context.drawImage(fig.imageObj, 0, 0);\n",
       "        };\n",
       "\n",
       "    this.imageObj.onunload = function() {\n",
       "        fig.ws.close();\n",
       "    }\n",
       "\n",
       "    this.ws.onmessage = this._make_on_message_function(this);\n",
       "\n",
       "    this.ondownload = ondownload;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_header = function() {\n",
       "    var titlebar = $(\n",
       "        '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
       "        'ui-helper-clearfix\"/>');\n",
       "    var titletext = $(\n",
       "        '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
       "        'text-align: center; padding: 3px;\"/>');\n",
       "    titlebar.append(titletext)\n",
       "    this.root.append(titlebar);\n",
       "    this.header = titletext[0];\n",
       "}\n",
       "\n",
       "\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_canvas = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var canvas_div = $('<div/>');\n",
       "\n",
       "    canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
       "\n",
       "    function canvas_keyboard_event(event) {\n",
       "        return fig.key_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    canvas_div.keydown('key_press', canvas_keyboard_event);\n",
       "    canvas_div.keyup('key_release', canvas_keyboard_event);\n",
       "    this.canvas_div = canvas_div\n",
       "    this._canvas_extra_style(canvas_div)\n",
       "    this.root.append(canvas_div);\n",
       "\n",
       "    var canvas = $('<canvas/>');\n",
       "    canvas.addClass('mpl-canvas');\n",
       "    canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
       "\n",
       "    this.canvas = canvas[0];\n",
       "    this.context = canvas[0].getContext(\"2d\");\n",
       "\n",
       "    var backingStore = this.context.backingStorePixelRatio ||\n",
       "\tthis.context.webkitBackingStorePixelRatio ||\n",
       "\tthis.context.mozBackingStorePixelRatio ||\n",
       "\tthis.context.msBackingStorePixelRatio ||\n",
       "\tthis.context.oBackingStorePixelRatio ||\n",
       "\tthis.context.backingStorePixelRatio || 1;\n",
       "\n",
       "    mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
       "\n",
       "    var rubberband = $('<canvas/>');\n",
       "    rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
       "\n",
       "    var pass_mouse_events = true;\n",
       "\n",
       "    canvas_div.resizable({\n",
       "        start: function(event, ui) {\n",
       "            pass_mouse_events = false;\n",
       "        },\n",
       "        resize: function(event, ui) {\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "        stop: function(event, ui) {\n",
       "            pass_mouse_events = true;\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "    });\n",
       "\n",
       "    function mouse_event_fn(event) {\n",
       "        if (pass_mouse_events)\n",
       "            return fig.mouse_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    rubberband.mousedown('button_press', mouse_event_fn);\n",
       "    rubberband.mouseup('button_release', mouse_event_fn);\n",
       "    // Throttle sequential mouse events to 1 every 20ms.\n",
       "    rubberband.mousemove('motion_notify', mouse_event_fn);\n",
       "\n",
       "    rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
       "    rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
       "\n",
       "    canvas_div.on(\"wheel\", function (event) {\n",
       "        event = event.originalEvent;\n",
       "        event['data'] = 'scroll'\n",
       "        if (event.deltaY < 0) {\n",
       "            event.step = 1;\n",
       "        } else {\n",
       "            event.step = -1;\n",
       "        }\n",
       "        mouse_event_fn(event);\n",
       "    });\n",
       "\n",
       "    canvas_div.append(canvas);\n",
       "    canvas_div.append(rubberband);\n",
       "\n",
       "    this.rubberband = rubberband;\n",
       "    this.rubberband_canvas = rubberband[0];\n",
       "    this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
       "    this.rubberband_context.strokeStyle = \"#000000\";\n",
       "\n",
       "    this._resize_canvas = function(width, height) {\n",
       "        // Keep the size of the canvas, canvas container, and rubber band\n",
       "        // canvas in synch.\n",
       "        canvas_div.css('width', width)\n",
       "        canvas_div.css('height', height)\n",
       "\n",
       "        canvas.attr('width', width * mpl.ratio);\n",
       "        canvas.attr('height', height * mpl.ratio);\n",
       "        canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
       "\n",
       "        rubberband.attr('width', width);\n",
       "        rubberband.attr('height', height);\n",
       "    }\n",
       "\n",
       "    // Set the figure to an initial 600x600px, this will subsequently be updated\n",
       "    // upon first draw.\n",
       "    this._resize_canvas(600, 600);\n",
       "\n",
       "    // Disable right mouse context menu.\n",
       "    $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
       "        return false;\n",
       "    });\n",
       "\n",
       "    function set_focus () {\n",
       "        canvas.focus();\n",
       "        canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    window.setTimeout(set_focus, 100);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            // put a spacer in here.\n",
       "            continue;\n",
       "        }\n",
       "        var button = $('<button/>');\n",
       "        button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
       "                        'ui-button-icon-only');\n",
       "        button.attr('role', 'button');\n",
       "        button.attr('aria-disabled', 'false');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "\n",
       "        var icon_img = $('<span/>');\n",
       "        icon_img.addClass('ui-button-icon-primary ui-icon');\n",
       "        icon_img.addClass(image);\n",
       "        icon_img.addClass('ui-corner-all');\n",
       "\n",
       "        var tooltip_span = $('<span/>');\n",
       "        tooltip_span.addClass('ui-button-text');\n",
       "        tooltip_span.html(tooltip);\n",
       "\n",
       "        button.append(icon_img);\n",
       "        button.append(tooltip_span);\n",
       "\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    var fmt_picker_span = $('<span/>');\n",
       "\n",
       "    var fmt_picker = $('<select/>');\n",
       "    fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
       "    fmt_picker_span.append(fmt_picker);\n",
       "    nav_element.append(fmt_picker_span);\n",
       "    this.format_dropdown = fmt_picker[0];\n",
       "\n",
       "    for (var ind in mpl.extensions) {\n",
       "        var fmt = mpl.extensions[ind];\n",
       "        var option = $(\n",
       "            '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
       "        fmt_picker.append(option);\n",
       "    }\n",
       "\n",
       "    // Add hover states to the ui-buttons\n",
       "    $( \".ui-button\" ).hover(\n",
       "        function() { $(this).addClass(\"ui-state-hover\");},\n",
       "        function() { $(this).removeClass(\"ui-state-hover\");}\n",
       "    );\n",
       "\n",
       "    var status_bar = $('<span class=\"mpl-message\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
       "    // which will in turn request a refresh of the image.\n",
       "    this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_message = function(type, properties) {\n",
       "    properties['type'] = type;\n",
       "    properties['figure_id'] = this.id;\n",
       "    this.ws.send(JSON.stringify(properties));\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_draw_message = function() {\n",
       "    if (!this.waiting) {\n",
       "        this.waiting = true;\n",
       "        this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
       "    }\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    var format_dropdown = fig.format_dropdown;\n",
       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
       "    fig.ondownload(fig, format);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
       "    var size = msg['size'];\n",
       "    if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
       "        fig._resize_canvas(size[0], size[1]);\n",
       "        fig.send_message(\"refresh\", {});\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
       "    var x0 = msg['x0'] / mpl.ratio;\n",
       "    var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
       "    var x1 = msg['x1'] / mpl.ratio;\n",
       "    var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
       "    x0 = Math.floor(x0) + 0.5;\n",
       "    y0 = Math.floor(y0) + 0.5;\n",
       "    x1 = Math.floor(x1) + 0.5;\n",
       "    y1 = Math.floor(y1) + 0.5;\n",
       "    var min_x = Math.min(x0, x1);\n",
       "    var min_y = Math.min(y0, y1);\n",
       "    var width = Math.abs(x1 - x0);\n",
       "    var height = Math.abs(y1 - y0);\n",
       "\n",
       "    fig.rubberband_context.clearRect(\n",
       "        0, 0, fig.canvas.width, fig.canvas.height);\n",
       "\n",
       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
       "    // Updates the figure title.\n",
       "    fig.header.textContent = msg['label'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
       "    var cursor = msg['cursor'];\n",
       "    switch(cursor)\n",
       "    {\n",
       "    case 0:\n",
       "        cursor = 'pointer';\n",
       "        break;\n",
       "    case 1:\n",
       "        cursor = 'default';\n",
       "        break;\n",
       "    case 2:\n",
       "        cursor = 'crosshair';\n",
       "        break;\n",
       "    case 3:\n",
       "        cursor = 'move';\n",
       "        break;\n",
       "    }\n",
       "    fig.rubberband_canvas.style.cursor = cursor;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
       "    fig.message.textContent = msg['message'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
       "    // Request the server to send over a new figure.\n",
       "    fig.send_draw_message();\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
       "    fig.image_mode = msg['mode'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Called whenever the canvas gets updated.\n",
       "    this.send_message(\"ack\", {});\n",
       "}\n",
       "\n",
       "// A function to construct a web socket function for onmessage handling.\n",
       "// Called in the figure constructor.\n",
       "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
       "    return function socket_on_message(evt) {\n",
       "        if (evt.data instanceof Blob) {\n",
       "            /* FIXME: We get \"Resource interpreted as Image but\n",
       "             * transferred with MIME type text/plain:\" errors on\n",
       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
       "             * to be part of the websocket stream */\n",
       "            evt.data.type = \"image/png\";\n",
       "\n",
       "            /* Free the memory for the previous frames */\n",
       "            if (fig.imageObj.src) {\n",
       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
       "                    fig.imageObj.src);\n",
       "            }\n",
       "\n",
       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
       "                evt.data);\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "        else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
       "            fig.imageObj.src = evt.data;\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        var msg = JSON.parse(evt.data);\n",
       "        var msg_type = msg['type'];\n",
       "\n",
       "        // Call the  \"handle_{type}\" callback, which takes\n",
       "        // the figure and JSON message as its only arguments.\n",
       "        try {\n",
       "            var callback = fig[\"handle_\" + msg_type];\n",
       "        } catch (e) {\n",
       "            console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        if (callback) {\n",
       "            try {\n",
       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
       "                callback(fig, msg);\n",
       "            } catch (e) {\n",
       "                console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
       "            }\n",
       "        }\n",
       "    };\n",
       "}\n",
       "\n",
       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
       "mpl.findpos = function(e) {\n",
       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
       "    var targ;\n",
       "    if (!e)\n",
       "        e = window.event;\n",
       "    if (e.target)\n",
       "        targ = e.target;\n",
       "    else if (e.srcElement)\n",
       "        targ = e.srcElement;\n",
       "    if (targ.nodeType == 3) // defeat Safari bug\n",
       "        targ = targ.parentNode;\n",
       "\n",
       "    // jQuery normalizes the pageX and pageY\n",
       "    // pageX,Y are the mouse positions relative to the document\n",
       "    // offset() returns the position of the element relative to the document\n",
       "    var x = e.pageX - $(targ).offset().left;\n",
       "    var y = e.pageY - $(targ).offset().top;\n",
       "\n",
       "    return {\"x\": x, \"y\": y};\n",
       "};\n",
       "\n",
       "/*\n",
       " * return a copy of an object with only non-object keys\n",
       " * we need this to avoid circular references\n",
       " * http://stackoverflow.com/a/24161582/3208463\n",
       " */\n",
       "function simpleKeys (original) {\n",
       "  return Object.keys(original).reduce(function (obj, key) {\n",
       "    if (typeof original[key] !== 'object')\n",
       "        obj[key] = original[key]\n",
       "    return obj;\n",
       "  }, {});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.mouse_event = function(event, name) {\n",
       "    var canvas_pos = mpl.findpos(event)\n",
       "\n",
       "    if (name === 'button_press')\n",
       "    {\n",
       "        this.canvas.focus();\n",
       "        this.canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    var x = canvas_pos.x * mpl.ratio;\n",
       "    var y = canvas_pos.y * mpl.ratio;\n",
       "\n",
       "    this.send_message(name, {x: x, y: y, button: event.button,\n",
       "                             step: event.step,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "\n",
       "    /* This prevents the web browser from automatically changing to\n",
       "     * the text insertion cursor when the button is pressed.  We want\n",
       "     * to control all of the cursor setting manually through the\n",
       "     * 'cursor' event from matplotlib */\n",
       "    event.preventDefault();\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    // Handle any extra behaviour associated with a key event\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.key_event = function(event, name) {\n",
       "\n",
       "    // Prevent repeat events\n",
       "    if (name == 'key_press')\n",
       "    {\n",
       "        if (event.which === this._key)\n",
       "            return;\n",
       "        else\n",
       "            this._key = event.which;\n",
       "    }\n",
       "    if (name == 'key_release')\n",
       "        this._key = null;\n",
       "\n",
       "    var value = '';\n",
       "    if (event.ctrlKey && event.which != 17)\n",
       "        value += \"ctrl+\";\n",
       "    if (event.altKey && event.which != 18)\n",
       "        value += \"alt+\";\n",
       "    if (event.shiftKey && event.which != 16)\n",
       "        value += \"shift+\";\n",
       "\n",
       "    value += 'k';\n",
       "    value += event.which.toString();\n",
       "\n",
       "    this._key_event_extra(event, name);\n",
       "\n",
       "    this.send_message(name, {key: value,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
       "    if (name == 'download') {\n",
       "        this.handle_save(this, null);\n",
       "    } else {\n",
       "        this.send_message(\"toolbar_button\", {name: name});\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
       "    this.message.textContent = tooltip;\n",
       "};\n",
       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
       "\n",
       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
       "\n",
       "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
       "    // object with the appropriate methods. Currently this is a non binary\n",
       "    // socket, so there is still some room for performance tuning.\n",
       "    var ws = {};\n",
       "\n",
       "    ws.close = function() {\n",
       "        comm.close()\n",
       "    };\n",
       "    ws.send = function(m) {\n",
       "        //console.log('sending', m);\n",
       "        comm.send(m);\n",
       "    };\n",
       "    // Register the callback with on_msg.\n",
       "    comm.on_msg(function(msg) {\n",
       "        //console.log('receiving', msg['content']['data'], msg);\n",
       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
       "        ws.onmessage(msg['content']['data'])\n",
       "    });\n",
       "    return ws;\n",
       "}\n",
       "\n",
       "mpl.mpl_figure_comm = function(comm, msg) {\n",
       "    // This is the function which gets called when the mpl process\n",
       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
       "\n",
       "    var id = msg.content.data.id;\n",
       "    // Get hold of the div created by the display call when the Comm\n",
       "    // socket was opened in Python.\n",
       "    var element = $(\"#\" + id);\n",
       "    var ws_proxy = comm_websocket_adapter(comm)\n",
       "\n",
       "    function ondownload(figure, format) {\n",
       "        window.open(figure.imageObj.src);\n",
       "    }\n",
       "\n",
       "    var fig = new mpl.figure(id, ws_proxy,\n",
       "                           ondownload,\n",
       "                           element.get(0));\n",
       "\n",
       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
       "    // web socket which is closed, not our websocket->open comm proxy.\n",
       "    ws_proxy.onopen();\n",
       "\n",
       "    fig.parent_element = element.get(0);\n",
       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
       "    if (!fig.cell_info) {\n",
       "        console.error(\"Failed to find cell for figure\", id, fig);\n",
       "        return;\n",
       "    }\n",
       "\n",
       "    var output_index = fig.cell_info[2]\n",
       "    var cell = fig.cell_info[0];\n",
       "\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
       "    var width = fig.canvas.width/mpl.ratio\n",
       "    fig.root.unbind('remove')\n",
       "\n",
       "    // Update the output cell to use the data from the current canvas.\n",
       "    fig.push_to_output();\n",
       "    var dataURL = fig.canvas.toDataURL();\n",
       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
       "    // the notebook keyboard shortcuts fail.\n",
       "    IPython.keyboard_manager.enable()\n",
       "    $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
       "    fig.close_ws(fig, msg);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.close_ws = function(fig, msg){\n",
       "    fig.send_message('closing', msg);\n",
       "    // fig.ws.close()\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
       "    // Turn the data on the canvas into data in the output cell.\n",
       "    var width = this.canvas.width/mpl.ratio\n",
       "    var dataURL = this.canvas.toDataURL();\n",
       "    this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Tell IPython that the notebook contents must change.\n",
       "    IPython.notebook.set_dirty(true);\n",
       "    this.send_message(\"ack\", {});\n",
       "    var fig = this;\n",
       "    // Wait a second, then push the new image to the DOM so\n",
       "    // that it is saved nicely (might be nice to debounce this).\n",
       "    setTimeout(function () { fig.push_to_output() }, 1000);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items){\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) { continue; };\n",
       "\n",
       "        var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    // Add the status bar.\n",
       "    var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "\n",
       "    // Add the close button to the window.\n",
       "    var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
       "    var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
       "    button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
       "    button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
       "    buttongrp.append(button);\n",
       "    var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
       "    titlebar.prepend(buttongrp);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(el){\n",
       "    var fig = this\n",
       "    el.on(\"remove\", function(){\n",
       "\tfig.close_ws(fig, {});\n",
       "    });\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(el){\n",
       "    // this is important to make the div 'focusable\n",
       "    el.attr('tabindex', 0)\n",
       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
       "    // off when our div gets focus\n",
       "\n",
       "    // location in version 3\n",
       "    if (IPython.notebook.keyboard_manager) {\n",
       "        IPython.notebook.keyboard_manager.register_events(el);\n",
       "    }\n",
       "    else {\n",
       "        // location in version 2\n",
       "        IPython.keyboard_manager.register_events(el);\n",
       "    }\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    var manager = IPython.notebook.keyboard_manager;\n",
       "    if (!manager)\n",
       "        manager = IPython.keyboard_manager;\n",
       "\n",
       "    // Check for shift+enter\n",
       "    if (event.shiftKey && event.which == 13) {\n",
       "        this.canvas_div.blur();\n",
       "        event.shiftKey = false;\n",
       "        // Send a \"J\" for go to next cell\n",
       "        event.which = 74;\n",
       "        event.keyCode = 74;\n",
       "        manager.command_mode();\n",
       "        manager.handle_keydown(event);\n",
       "    }\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    fig.ondownload(fig, null);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.find_output_cell = function(html_output) {\n",
       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
       "    // IPython event is triggered only after the cells have been serialised, which for\n",
       "    // our purposes (turning an active figure into a static one), is too late.\n",
       "    var cells = IPython.notebook.get_cells();\n",
       "    var ncells = cells.length;\n",
       "    for (var i=0; i<ncells; i++) {\n",
       "        var cell = cells[i];\n",
       "        if (cell.cell_type === 'code'){\n",
       "            for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
       "                var data = cell.output_area.outputs[j];\n",
       "                if (data.data) {\n",
       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
       "                    data = data.data;\n",
       "                }\n",
       "                if (data['text/html'] == html_output) {\n",
       "                    return [cell, data, j];\n",
       "                }\n",
       "            }\n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "// Register the function which deals with the matplotlib target/channel.\n",
       "// The kernel may be null if the page has been refreshed.\n",
       "if (IPython.notebook.kernel != null) {\n",
       "    IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
       "}\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img src=\"\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(freq_full_list, alpha_full_list[:, 2, 2], label=\"Gaussian\")\n",
    "ax.plot(freq_full_list, alpha_zz_full_res, linestyle=\"-.\", c=\"C2\", label=\"Resonance\")\n",
    "ax.plot(freq_full_list, alpha_zz_full_nonres, linestyle=\"-.\", c=\"C3\", label=\"Non-Resonance\")\n",
    "ax.plot(freq_full_list, alpha_zz_full_calc, linestyle=\":\", label=\"Calculated\")\n",
    "rect = patches.Rectangle((0.184, -24), 0.01, 78, linewidth=1, edgecolor='C4', facecolor='C4', alpha=.25)\n",
    "ax.add_patch(rect)\n",
    "ax.set_ylim(-25, 75)\n",
    "ax.set_xlabel(r\"$\\omega$ / $E_\\mathrm{h}$\")\n",
    "ax.set_ylabel(r\"$\\alpha_{zz} (\\omega)$ / a.u.\")\n",
    "ax.set_title(\"Frequency-Dependent Polarizability of $\\mathrm{H_2O_2}$ (RHF/6-31G)\")\n",
    "ax.legend()\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于前两个激发态能量的窄区间中，我们的结果在非断点附近其实与 Gaussian 的结果也基本一致："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "alpha_zz_small_calc   = np.vectorize(lambda omega: freq_to_alpha (omega)[2, 2])(freq_small_list)\n",
    "alpha_zz_small_res    = np.vectorize(lambda omega: freq_to_res   (omega)[2, 2])(freq_small_list)\n",
    "alpha_zz_small_nonres = np.vectorize(lambda omega: freq_to_nonres(omega)[2, 2])(freq_small_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "/* Put everything inside the global mpl namespace */\n",
       "window.mpl = {};\n",
       "\n",
       "\n",
       "mpl.get_websocket_type = function() {\n",
       "    if (typeof(WebSocket) !== 'undefined') {\n",
       "        return WebSocket;\n",
       "    } else if (typeof(MozWebSocket) !== 'undefined') {\n",
       "        return MozWebSocket;\n",
       "    } else {\n",
       "        alert('Your browser does not have WebSocket support. ' +\n",
       "              'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
       "              'Firefox 4 and 5 are also supported but you ' +\n",
       "              'have to enable WebSockets in about:config.');\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
       "    this.id = figure_id;\n",
       "\n",
       "    this.ws = websocket;\n",
       "\n",
       "    this.supports_binary = (this.ws.binaryType != undefined);\n",
       "\n",
       "    if (!this.supports_binary) {\n",
       "        var warnings = document.getElementById(\"mpl-warnings\");\n",
       "        if (warnings) {\n",
       "            warnings.style.display = 'block';\n",
       "            warnings.textContent = (\n",
       "                \"This browser does not support binary websocket messages. \" +\n",
       "                    \"Performance may be slow.\");\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.imageObj = new Image();\n",
       "\n",
       "    this.context = undefined;\n",
       "    this.message = undefined;\n",
       "    this.canvas = undefined;\n",
       "    this.rubberband_canvas = undefined;\n",
       "    this.rubberband_context = undefined;\n",
       "    this.format_dropdown = undefined;\n",
       "\n",
       "    this.image_mode = 'full';\n",
       "\n",
       "    this.root = $('<div/>');\n",
       "    this._root_extra_style(this.root)\n",
       "    this.root.attr('style', 'display: inline-block');\n",
       "\n",
       "    $(parent_element).append(this.root);\n",
       "\n",
       "    this._init_header(this);\n",
       "    this._init_canvas(this);\n",
       "    this._init_toolbar(this);\n",
       "\n",
       "    var fig = this;\n",
       "\n",
       "    this.waiting = false;\n",
       "\n",
       "    this.ws.onopen =  function () {\n",
       "            fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
       "            fig.send_message(\"send_image_mode\", {});\n",
       "            if (mpl.ratio != 1) {\n",
       "                fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
       "            }\n",
       "            fig.send_message(\"refresh\", {});\n",
       "        }\n",
       "\n",
       "    this.imageObj.onload = function() {\n",
       "            if (fig.image_mode == 'full') {\n",
       "                // Full images could contain transparency (where diff images\n",
       "                // almost always do), so we need to clear the canvas so that\n",
       "                // there is no ghosting.\n",
       "                fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
       "            }\n",
       "            fig.context.drawImage(fig.imageObj, 0, 0);\n",
       "        };\n",
       "\n",
       "    this.imageObj.onunload = function() {\n",
       "        fig.ws.close();\n",
       "    }\n",
       "\n",
       "    this.ws.onmessage = this._make_on_message_function(this);\n",
       "\n",
       "    this.ondownload = ondownload;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_header = function() {\n",
       "    var titlebar = $(\n",
       "        '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
       "        'ui-helper-clearfix\"/>');\n",
       "    var titletext = $(\n",
       "        '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
       "        'text-align: center; padding: 3px;\"/>');\n",
       "    titlebar.append(titletext)\n",
       "    this.root.append(titlebar);\n",
       "    this.header = titletext[0];\n",
       "}\n",
       "\n",
       "\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_canvas = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var canvas_div = $('<div/>');\n",
       "\n",
       "    canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
       "\n",
       "    function canvas_keyboard_event(event) {\n",
       "        return fig.key_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    canvas_div.keydown('key_press', canvas_keyboard_event);\n",
       "    canvas_div.keyup('key_release', canvas_keyboard_event);\n",
       "    this.canvas_div = canvas_div\n",
       "    this._canvas_extra_style(canvas_div)\n",
       "    this.root.append(canvas_div);\n",
       "\n",
       "    var canvas = $('<canvas/>');\n",
       "    canvas.addClass('mpl-canvas');\n",
       "    canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
       "\n",
       "    this.canvas = canvas[0];\n",
       "    this.context = canvas[0].getContext(\"2d\");\n",
       "\n",
       "    var backingStore = this.context.backingStorePixelRatio ||\n",
       "\tthis.context.webkitBackingStorePixelRatio ||\n",
       "\tthis.context.mozBackingStorePixelRatio ||\n",
       "\tthis.context.msBackingStorePixelRatio ||\n",
       "\tthis.context.oBackingStorePixelRatio ||\n",
       "\tthis.context.backingStorePixelRatio || 1;\n",
       "\n",
       "    mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
       "\n",
       "    var rubberband = $('<canvas/>');\n",
       "    rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
       "\n",
       "    var pass_mouse_events = true;\n",
       "\n",
       "    canvas_div.resizable({\n",
       "        start: function(event, ui) {\n",
       "            pass_mouse_events = false;\n",
       "        },\n",
       "        resize: function(event, ui) {\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "        stop: function(event, ui) {\n",
       "            pass_mouse_events = true;\n",
       "            fig.request_resize(ui.size.width, ui.size.height);\n",
       "        },\n",
       "    });\n",
       "\n",
       "    function mouse_event_fn(event) {\n",
       "        if (pass_mouse_events)\n",
       "            return fig.mouse_event(event, event['data']);\n",
       "    }\n",
       "\n",
       "    rubberband.mousedown('button_press', mouse_event_fn);\n",
       "    rubberband.mouseup('button_release', mouse_event_fn);\n",
       "    // Throttle sequential mouse events to 1 every 20ms.\n",
       "    rubberband.mousemove('motion_notify', mouse_event_fn);\n",
       "\n",
       "    rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
       "    rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
       "\n",
       "    canvas_div.on(\"wheel\", function (event) {\n",
       "        event = event.originalEvent;\n",
       "        event['data'] = 'scroll'\n",
       "        if (event.deltaY < 0) {\n",
       "            event.step = 1;\n",
       "        } else {\n",
       "            event.step = -1;\n",
       "        }\n",
       "        mouse_event_fn(event);\n",
       "    });\n",
       "\n",
       "    canvas_div.append(canvas);\n",
       "    canvas_div.append(rubberband);\n",
       "\n",
       "    this.rubberband = rubberband;\n",
       "    this.rubberband_canvas = rubberband[0];\n",
       "    this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
       "    this.rubberband_context.strokeStyle = \"#000000\";\n",
       "\n",
       "    this._resize_canvas = function(width, height) {\n",
       "        // Keep the size of the canvas, canvas container, and rubber band\n",
       "        // canvas in synch.\n",
       "        canvas_div.css('width', width)\n",
       "        canvas_div.css('height', height)\n",
       "\n",
       "        canvas.attr('width', width * mpl.ratio);\n",
       "        canvas.attr('height', height * mpl.ratio);\n",
       "        canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
       "\n",
       "        rubberband.attr('width', width);\n",
       "        rubberband.attr('height', height);\n",
       "    }\n",
       "\n",
       "    // Set the figure to an initial 600x600px, this will subsequently be updated\n",
       "    // upon first draw.\n",
       "    this._resize_canvas(600, 600);\n",
       "\n",
       "    // Disable right mouse context menu.\n",
       "    $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
       "        return false;\n",
       "    });\n",
       "\n",
       "    function set_focus () {\n",
       "        canvas.focus();\n",
       "        canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    window.setTimeout(set_focus, 100);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            // put a spacer in here.\n",
       "            continue;\n",
       "        }\n",
       "        var button = $('<button/>');\n",
       "        button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
       "                        'ui-button-icon-only');\n",
       "        button.attr('role', 'button');\n",
       "        button.attr('aria-disabled', 'false');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "\n",
       "        var icon_img = $('<span/>');\n",
       "        icon_img.addClass('ui-button-icon-primary ui-icon');\n",
       "        icon_img.addClass(image);\n",
       "        icon_img.addClass('ui-corner-all');\n",
       "\n",
       "        var tooltip_span = $('<span/>');\n",
       "        tooltip_span.addClass('ui-button-text');\n",
       "        tooltip_span.html(tooltip);\n",
       "\n",
       "        button.append(icon_img);\n",
       "        button.append(tooltip_span);\n",
       "\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    var fmt_picker_span = $('<span/>');\n",
       "\n",
       "    var fmt_picker = $('<select/>');\n",
       "    fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
       "    fmt_picker_span.append(fmt_picker);\n",
       "    nav_element.append(fmt_picker_span);\n",
       "    this.format_dropdown = fmt_picker[0];\n",
       "\n",
       "    for (var ind in mpl.extensions) {\n",
       "        var fmt = mpl.extensions[ind];\n",
       "        var option = $(\n",
       "            '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
       "        fmt_picker.append(option);\n",
       "    }\n",
       "\n",
       "    // Add hover states to the ui-buttons\n",
       "    $( \".ui-button\" ).hover(\n",
       "        function() { $(this).addClass(\"ui-state-hover\");},\n",
       "        function() { $(this).removeClass(\"ui-state-hover\");}\n",
       "    );\n",
       "\n",
       "    var status_bar = $('<span class=\"mpl-message\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
       "    // which will in turn request a refresh of the image.\n",
       "    this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_message = function(type, properties) {\n",
       "    properties['type'] = type;\n",
       "    properties['figure_id'] = this.id;\n",
       "    this.ws.send(JSON.stringify(properties));\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.send_draw_message = function() {\n",
       "    if (!this.waiting) {\n",
       "        this.waiting = true;\n",
       "        this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
       "    }\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    var format_dropdown = fig.format_dropdown;\n",
       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
       "    fig.ondownload(fig, format);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
       "    var size = msg['size'];\n",
       "    if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
       "        fig._resize_canvas(size[0], size[1]);\n",
       "        fig.send_message(\"refresh\", {});\n",
       "    };\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
       "    var x0 = msg['x0'] / mpl.ratio;\n",
       "    var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
       "    var x1 = msg['x1'] / mpl.ratio;\n",
       "    var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
       "    x0 = Math.floor(x0) + 0.5;\n",
       "    y0 = Math.floor(y0) + 0.5;\n",
       "    x1 = Math.floor(x1) + 0.5;\n",
       "    y1 = Math.floor(y1) + 0.5;\n",
       "    var min_x = Math.min(x0, x1);\n",
       "    var min_y = Math.min(y0, y1);\n",
       "    var width = Math.abs(x1 - x0);\n",
       "    var height = Math.abs(y1 - y0);\n",
       "\n",
       "    fig.rubberband_context.clearRect(\n",
       "        0, 0, fig.canvas.width, fig.canvas.height);\n",
       "\n",
       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
       "    // Updates the figure title.\n",
       "    fig.header.textContent = msg['label'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
       "    var cursor = msg['cursor'];\n",
       "    switch(cursor)\n",
       "    {\n",
       "    case 0:\n",
       "        cursor = 'pointer';\n",
       "        break;\n",
       "    case 1:\n",
       "        cursor = 'default';\n",
       "        break;\n",
       "    case 2:\n",
       "        cursor = 'crosshair';\n",
       "        break;\n",
       "    case 3:\n",
       "        cursor = 'move';\n",
       "        break;\n",
       "    }\n",
       "    fig.rubberband_canvas.style.cursor = cursor;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
       "    fig.message.textContent = msg['message'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
       "    // Request the server to send over a new figure.\n",
       "    fig.send_draw_message();\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
       "    fig.image_mode = msg['mode'];\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Called whenever the canvas gets updated.\n",
       "    this.send_message(\"ack\", {});\n",
       "}\n",
       "\n",
       "// A function to construct a web socket function for onmessage handling.\n",
       "// Called in the figure constructor.\n",
       "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
       "    return function socket_on_message(evt) {\n",
       "        if (evt.data instanceof Blob) {\n",
       "            /* FIXME: We get \"Resource interpreted as Image but\n",
       "             * transferred with MIME type text/plain:\" errors on\n",
       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
       "             * to be part of the websocket stream */\n",
       "            evt.data.type = \"image/png\";\n",
       "\n",
       "            /* Free the memory for the previous frames */\n",
       "            if (fig.imageObj.src) {\n",
       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
       "                    fig.imageObj.src);\n",
       "            }\n",
       "\n",
       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
       "                evt.data);\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "        else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
       "            fig.imageObj.src = evt.data;\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        var msg = JSON.parse(evt.data);\n",
       "        var msg_type = msg['type'];\n",
       "\n",
       "        // Call the  \"handle_{type}\" callback, which takes\n",
       "        // the figure and JSON message as its only arguments.\n",
       "        try {\n",
       "            var callback = fig[\"handle_\" + msg_type];\n",
       "        } catch (e) {\n",
       "            console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        if (callback) {\n",
       "            try {\n",
       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
       "                callback(fig, msg);\n",
       "            } catch (e) {\n",
       "                console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
       "            }\n",
       "        }\n",
       "    };\n",
       "}\n",
       "\n",
       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
       "mpl.findpos = function(e) {\n",
       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
       "    var targ;\n",
       "    if (!e)\n",
       "        e = window.event;\n",
       "    if (e.target)\n",
       "        targ = e.target;\n",
       "    else if (e.srcElement)\n",
       "        targ = e.srcElement;\n",
       "    if (targ.nodeType == 3) // defeat Safari bug\n",
       "        targ = targ.parentNode;\n",
       "\n",
       "    // jQuery normalizes the pageX and pageY\n",
       "    // pageX,Y are the mouse positions relative to the document\n",
       "    // offset() returns the position of the element relative to the document\n",
       "    var x = e.pageX - $(targ).offset().left;\n",
       "    var y = e.pageY - $(targ).offset().top;\n",
       "\n",
       "    return {\"x\": x, \"y\": y};\n",
       "};\n",
       "\n",
       "/*\n",
       " * return a copy of an object with only non-object keys\n",
       " * we need this to avoid circular references\n",
       " * http://stackoverflow.com/a/24161582/3208463\n",
       " */\n",
       "function simpleKeys (original) {\n",
       "  return Object.keys(original).reduce(function (obj, key) {\n",
       "    if (typeof original[key] !== 'object')\n",
       "        obj[key] = original[key]\n",
       "    return obj;\n",
       "  }, {});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.mouse_event = function(event, name) {\n",
       "    var canvas_pos = mpl.findpos(event)\n",
       "\n",
       "    if (name === 'button_press')\n",
       "    {\n",
       "        this.canvas.focus();\n",
       "        this.canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    var x = canvas_pos.x * mpl.ratio;\n",
       "    var y = canvas_pos.y * mpl.ratio;\n",
       "\n",
       "    this.send_message(name, {x: x, y: y, button: event.button,\n",
       "                             step: event.step,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "\n",
       "    /* This prevents the web browser from automatically changing to\n",
       "     * the text insertion cursor when the button is pressed.  We want\n",
       "     * to control all of the cursor setting manually through the\n",
       "     * 'cursor' event from matplotlib */\n",
       "    event.preventDefault();\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    // Handle any extra behaviour associated with a key event\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.key_event = function(event, name) {\n",
       "\n",
       "    // Prevent repeat events\n",
       "    if (name == 'key_press')\n",
       "    {\n",
       "        if (event.which === this._key)\n",
       "            return;\n",
       "        else\n",
       "            this._key = event.which;\n",
       "    }\n",
       "    if (name == 'key_release')\n",
       "        this._key = null;\n",
       "\n",
       "    var value = '';\n",
       "    if (event.ctrlKey && event.which != 17)\n",
       "        value += \"ctrl+\";\n",
       "    if (event.altKey && event.which != 18)\n",
       "        value += \"alt+\";\n",
       "    if (event.shiftKey && event.which != 16)\n",
       "        value += \"shift+\";\n",
       "\n",
       "    value += 'k';\n",
       "    value += event.which.toString();\n",
       "\n",
       "    this._key_event_extra(event, name);\n",
       "\n",
       "    this.send_message(name, {key: value,\n",
       "                             guiEvent: simpleKeys(event)});\n",
       "    return false;\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
       "    if (name == 'download') {\n",
       "        this.handle_save(this, null);\n",
       "    } else {\n",
       "        this.send_message(\"toolbar_button\", {name: name});\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
       "    this.message.textContent = tooltip;\n",
       "};\n",
       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
       "\n",
       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
       "\n",
       "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
       "    // object with the appropriate methods. Currently this is a non binary\n",
       "    // socket, so there is still some room for performance tuning.\n",
       "    var ws = {};\n",
       "\n",
       "    ws.close = function() {\n",
       "        comm.close()\n",
       "    };\n",
       "    ws.send = function(m) {\n",
       "        //console.log('sending', m);\n",
       "        comm.send(m);\n",
       "    };\n",
       "    // Register the callback with on_msg.\n",
       "    comm.on_msg(function(msg) {\n",
       "        //console.log('receiving', msg['content']['data'], msg);\n",
       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
       "        ws.onmessage(msg['content']['data'])\n",
       "    });\n",
       "    return ws;\n",
       "}\n",
       "\n",
       "mpl.mpl_figure_comm = function(comm, msg) {\n",
       "    // This is the function which gets called when the mpl process\n",
       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
       "\n",
       "    var id = msg.content.data.id;\n",
       "    // Get hold of the div created by the display call when the Comm\n",
       "    // socket was opened in Python.\n",
       "    var element = $(\"#\" + id);\n",
       "    var ws_proxy = comm_websocket_adapter(comm)\n",
       "\n",
       "    function ondownload(figure, format) {\n",
       "        window.open(figure.imageObj.src);\n",
       "    }\n",
       "\n",
       "    var fig = new mpl.figure(id, ws_proxy,\n",
       "                           ondownload,\n",
       "                           element.get(0));\n",
       "\n",
       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
       "    // web socket which is closed, not our websocket->open comm proxy.\n",
       "    ws_proxy.onopen();\n",
       "\n",
       "    fig.parent_element = element.get(0);\n",
       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
       "    if (!fig.cell_info) {\n",
       "        console.error(\"Failed to find cell for figure\", id, fig);\n",
       "        return;\n",
       "    }\n",
       "\n",
       "    var output_index = fig.cell_info[2]\n",
       "    var cell = fig.cell_info[0];\n",
       "\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
       "    var width = fig.canvas.width/mpl.ratio\n",
       "    fig.root.unbind('remove')\n",
       "\n",
       "    // Update the output cell to use the data from the current canvas.\n",
       "    fig.push_to_output();\n",
       "    var dataURL = fig.canvas.toDataURL();\n",
       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
       "    // the notebook keyboard shortcuts fail.\n",
       "    IPython.keyboard_manager.enable()\n",
       "    $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
       "    fig.close_ws(fig, msg);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.close_ws = function(fig, msg){\n",
       "    fig.send_message('closing', msg);\n",
       "    // fig.ws.close()\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
       "    // Turn the data on the canvas into data in the output cell.\n",
       "    var width = this.canvas.width/mpl.ratio\n",
       "    var dataURL = this.canvas.toDataURL();\n",
       "    this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function() {\n",
       "    // Tell IPython that the notebook contents must change.\n",
       "    IPython.notebook.set_dirty(true);\n",
       "    this.send_message(\"ack\", {});\n",
       "    var fig = this;\n",
       "    // Wait a second, then push the new image to the DOM so\n",
       "    // that it is saved nicely (might be nice to debounce this).\n",
       "    setTimeout(function () { fig.push_to_output() }, 1000);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function() {\n",
       "    var fig = this;\n",
       "\n",
       "    var nav_element = $('<div/>');\n",
       "    nav_element.attr('style', 'width: 100%');\n",
       "    this.root.append(nav_element);\n",
       "\n",
       "    // Define a callback function for later on.\n",
       "    function toolbar_event(event) {\n",
       "        return fig.toolbar_button_onclick(event['data']);\n",
       "    }\n",
       "    function toolbar_mouse_event(event) {\n",
       "        return fig.toolbar_button_onmouseover(event['data']);\n",
       "    }\n",
       "\n",
       "    for(var toolbar_ind in mpl.toolbar_items){\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) { continue; };\n",
       "\n",
       "        var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
       "        button.click(method_name, toolbar_event);\n",
       "        button.mouseover(tooltip, toolbar_mouse_event);\n",
       "        nav_element.append(button);\n",
       "    }\n",
       "\n",
       "    // Add the status bar.\n",
       "    var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
       "    nav_element.append(status_bar);\n",
       "    this.message = status_bar[0];\n",
       "\n",
       "    // Add the close button to the window.\n",
       "    var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
       "    var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
       "    button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
       "    button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
       "    buttongrp.append(button);\n",
       "    var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
       "    titlebar.prepend(buttongrp);\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function(el){\n",
       "    var fig = this\n",
       "    el.on(\"remove\", function(){\n",
       "\tfig.close_ws(fig, {});\n",
       "    });\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function(el){\n",
       "    // this is important to make the div 'focusable\n",
       "    el.attr('tabindex', 0)\n",
       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
       "    // off when our div gets focus\n",
       "\n",
       "    // location in version 3\n",
       "    if (IPython.notebook.keyboard_manager) {\n",
       "        IPython.notebook.keyboard_manager.register_events(el);\n",
       "    }\n",
       "    else {\n",
       "        // location in version 2\n",
       "        IPython.keyboard_manager.register_events(el);\n",
       "    }\n",
       "\n",
       "}\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
       "    var manager = IPython.notebook.keyboard_manager;\n",
       "    if (!manager)\n",
       "        manager = IPython.keyboard_manager;\n",
       "\n",
       "    // Check for shift+enter\n",
       "    if (event.shiftKey && event.which == 13) {\n",
       "        this.canvas_div.blur();\n",
       "        event.shiftKey = false;\n",
       "        // Send a \"J\" for go to next cell\n",
       "        event.which = 74;\n",
       "        event.keyCode = 74;\n",
       "        manager.command_mode();\n",
       "        manager.handle_keydown(event);\n",
       "    }\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
       "    fig.ondownload(fig, null);\n",
       "}\n",
       "\n",
       "\n",
       "mpl.find_output_cell = function(html_output) {\n",
       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
       "    // IPython event is triggered only after the cells have been serialised, which for\n",
       "    // our purposes (turning an active figure into a static one), is too late.\n",
       "    var cells = IPython.notebook.get_cells();\n",
       "    var ncells = cells.length;\n",
       "    for (var i=0; i<ncells; i++) {\n",
       "        var cell = cells[i];\n",
       "        if (cell.cell_type === 'code'){\n",
       "            for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
       "                var data = cell.output_area.outputs[j];\n",
       "                if (data.data) {\n",
       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
       "                    data = data.data;\n",
       "                }\n",
       "                if (data['text/html'] == html_output) {\n",
       "                    return [cell, data, j];\n",
       "                }\n",
       "            }\n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "// Register the function which deals with the matplotlib target/channel.\n",
       "// The kernel may be null if the page has been refreshed.\n",
       "if (IPython.notebook.kernel != null) {\n",
       "    IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
       "}\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img src=\"\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(freq_small_list, alpha_small_list[:, 2, 2], label=\"Gaussian\")\n",
    "ax.plot(freq_small_list, alpha_zz_small_res, linestyle=\"-.\", c=\"C2\", label=\"Resonance\")\n",
    "ax.plot(freq_small_list, alpha_zz_small_nonres, linestyle=\"-.\", c=\"C3\", label=\"Non-Resonance\")\n",
    "ax.plot(freq_small_list, alpha_zz_small_calc, linestyle=\":\", label=\"Calculated\")\n",
    "ax.set_xlabel(r\"$\\omega$ / $E_\\mathrm{h}$\")\n",
    "ax.set_ylabel(r\"$\\alpha_{zz} (\\omega)$ / a.u.\")\n",
    "ax.set_title(\"Frequency-Dependent Polarizability of $\\mathrm{H_2O_2}$ (RHF/6-31G)\\nFor First Two Excited States\")\n",
    "ax.legend()\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们认为我们确实正确计算了含频极化率。在断点附近的行为应当被认为是数值上的微小差别；并且我们认为，在断点 (共振) 处附近产生的极化率的主要贡献部分应为极化率的共振项所产生。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TD-HF 方程含频极化率及其与跃迁偶极矩的关联"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上一大段中，我们仅仅是用了 TD-HF 给出的跃迁偶极矩结果，反推出了 CP-HF 的公式。但我们并没有介绍过最原始的 TD-HF 极化率表达式。下面我们会从最普遍的公式，推导含频极化率的表达式。下面的推导过程中，程序的部分会少一些。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们从 TD-DFT 的 Casida 方程开始；Casida 方程可以简单地退化到 TD-HF 的情形。我们上面在推演激发频率 $\\omega_n$ 时，也提到了 Casida 方程；\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\begin{pmatrix} \\mathbb{A} & \\mathbb{B} \\\\ - \\mathbb{B} & - \\mathbb{A} \\end{pmatrix}\n",
    "\\begin{pmatrix} \\mathbf{X}^n \\\\ \\mathbf{Y}^n \\end{pmatrix}\n",
    "= \\omega_n \\begin{pmatrix} \\mathbf{X}^n \\\\ \\mathbf{Y}^n \\end{pmatrix}\n",
    "\\tag{1}\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "但下面的 Casida 方程具有更为广泛的适用情形。我们引入外加的偶极微扰 `d_P` $\\mathbf{d}^t$ 与外加激发光束频率 `omega` $\\omega$ 的微扰，则有\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\begin{pmatrix} \\mathbb{A} & \\mathbb{B} \\\\ \\mathbb{B} & \\mathbb{A} \\end{pmatrix}\n",
    "\\begin{pmatrix} \\mathbf{X}'{}^t \\\\ \\mathbf{Y}'{}^t \\end{pmatrix}\n",
    "= \\omega \\begin{pmatrix} \\mathbf{X}'{}^t \\\\ - \\mathbf{Y}'{}^t \\end{pmatrix} +\n",
    "\\begin{pmatrix} 2 \\mathbf{d}^t \\\\ 2 \\mathbf{d}^t \\end{pmatrix}\n",
    "\\tag{2}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里有不少符号上的区别。首先，(1) 式的激发态频率 $\\omega_n$ 与 (2) 式的外加光束的频率 $\\omega$ 并不相同；但 (2) 在一种情形下确实地可以退化到 (1) 式。若现在没有外加偶极微扰 $\\mathbf{d}^t$，那么外加光束必须要恰好处于分子电子的激发频率上，分子的电子云微扰变化才能被允许 (即使时间非常短，这对应的是紫外光谱电子态的平均寿命)。而电子的激发频率不一定只有一个，因此会产生上下标 $n$ 表示不同的激发频率；第 $n$ 个激发态电子云微扰的形变大小和取向由 $\\mathbf{X}^n$ 与 $\\mathbf{Y}^n$ 共同决定；它们将会产生第 $n$ 个激发态的跃迁密度 $\\rho^n (\\boldsymbol{r}, \\omega)$ (下式对 $i, a$ 求和)：\n",
    "\n",
    "$$\n",
    "\\rho^n (\\boldsymbol{r}, \\omega_n) = (X_{ia}^n + Y_{ia}^n) \\phi_i (\\boldsymbol{r}) \\phi_a (\\boldsymbol{r})\n",
    "$$\n",
    "\n",
    "其次，(2) 式的 $\\mathbf{X}'{}^t$ 若不看偶极激发方向 $t$，它还比 (1) 式的 $\\mathbf{X}^n$ 少了 $n$ 并多了一撇；多的一撇是为了区分两者。之所以这里没有 $n$，我们可以这样考虑：在某一个特定的外加偶极微扰 $\\mathbf{d}^t$ 与频率 $\\omega$ 微扰下，分子的电子云确实会发生改变；但这种改变的方式一般是唯一的 (简并情况我们不作讨论)。这种形变所产生的密度形式也是类似的 (下式对 $i, a$ 求和)：\n",
    "\n",
    "$$\n",
    "\\rho (\\boldsymbol{r}, \\mathbf{d}^t, \\omega) = (X_{ia}'{}^t + Y_{ia}'{}^t) \\phi_i (\\boldsymbol{r}) \\phi_a (\\boldsymbol{r})\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "含频极化率可以通过上述的形变密度在偶极算符的作用下给出：\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega)\n",
    "= \\int -s \\cdot \\rho (\\boldsymbol{r}, \\mathbf{d}^t, \\omega) \\, \\mathrm{d} \\boldsymbol{r}\n",
    "= (X_{ia}'{}^t + Y_{ia}'{}^t) \\int -s \\cdot \\phi_i (\\boldsymbol{r}) \\phi_a (\\boldsymbol{r}) \\, \\mathrm{d} \\boldsymbol{r}\n",
    "= (X_{ia}'{}^t + Y_{ia}'{}^t) d_{ia}^s = (X_P'{}^t + Y_P'{}^t) d_P^s\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "其中关于 $X_P'{}^t, Y_P'{}^t$ 需要通过 Casida 方程求取。(2) 式经过简单的代数处理后得到 (3) 式：\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\begin{pmatrix} \\mathbb{A} - \\omega \\mathbb{1} & \\mathbb{B} \\\\ - \\mathbb{B} & - \\mathbb{A} - \\omega \\mathbb{1} \\end{pmatrix}\n",
    "\\begin{pmatrix} \\mathbf{X}'{}^t \\\\ \\mathbf{Y}'{}^t \\end{pmatrix}\n",
    "= \\begin{pmatrix} 2 \\mathbf{d}^t \\\\ - 2 \\mathbf{d}^t \\end{pmatrix}\n",
    "\\tag{3}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么我们有\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\alpha_{ts} (\\omega) = (X_P'{}^t + Y_P'{}^t) d_P^s\n",
    "= \\begin{pmatrix} \\mathbf{d}^s & \\mathbf{d}^s \\end{pmatrix} \\begin{pmatrix} \\mathbf{X}'{}^t \\\\ \\mathbf{Y}'{}^t \\end{pmatrix}\n",
    "= \\begin{pmatrix} \\mathbf{d}^s & \\mathbf{d}^s \\end{pmatrix}\n",
    "\\begin{pmatrix} \\mathbb{A} - \\omega \\mathbb{1} & \\mathbb{B} \\\\ - \\mathbb{B} & - \\mathbb{A} - \\omega \\mathbb{1} \\end{pmatrix}^{-1}\n",
    "\\begin{pmatrix} 2 \\mathbf{d}^t \\\\ - 2 \\mathbf{d}^t \\end{pmatrix}\n",
    "\\tag{4}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于不带频率的情形，我们可以用下面的代码验证："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 6.58142, -0.0841 , -1.45378],\n",
       "       [-0.0841 ,  4.26835,  0.39969],\n",
       "       [-1.45378,  0.39969, 17.89033]])"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.einsum(\"tP, PQ, sQ -> ts\",\n",
    "          np.concatenate([d_P, d_P], axis=1),\n",
    "          np.linalg.inv(AB),\n",
    "          np.concatenate([2 * d_P, - 2 * d_P], axis=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "而对于带频率的情形，我们可以举一个 $\\omega = 0.186 \\, E_\\mathrm{h}$ 的例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 7.28458, -0.05683, -2.08145],\n",
       "       [-0.05683,  4.79845, -1.39731],\n",
       "       [-2.08145, -1.39731, 37.73368]])"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "omega = 0.186\n",
    "np.einsum(\"tP, PQ, sQ -> ts\",\n",
    "          np.concatenate([d_P, d_P], axis=1),\n",
    "          np.linalg.inv(AB - np.eye(nvir*nocc*2) * omega),\n",
    "          np.concatenate([2 * d_P, - 2 * d_P], axis=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但这样的表达式 (4) 并没有出现跃迁偶极矩。下面我们需要简化表达式。\n",
    "\n",
    "首先，我们回顾式 (3)，得到方程组\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\mathbb{A} \\mathbf{X}'{}^t + \\mathbb{B} \\mathbf{Y}'{}^t - \\omega \\mathbf{X}'{}^t &= 2 \\mathbf{d}^t \\\\\n",
    "\\mathbb{B} \\mathbf{X}'{}^t + \\mathbb{A} \\mathbf{Y}'{}^t + \\omega \\mathbf{Y}'{}^t &= 2 \\mathbf{d}^t\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "两式加减后，可以得到\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "(\\mathbb{A} + \\mathbb{B}) (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) - \\omega (\\mathbf{X}'{}^t - \\mathbf{Y}'{}^t) &= 4 \\mathbf{d}^t \\tag{5} \\\\\n",
    "(\\mathbb{A} - \\mathbb{B}) (\\mathbf{X}'{}^t - \\mathbf{Y}'{}^t) &= \\omega (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) \\tag{6}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "利用 (6) 式替换 (5) 式中出现的 $(\\mathbf{X}'{}^t - \\mathbf{Y}'{}^t)$，有\n",
    "\n",
    "$$\n",
    "(\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) - \\omega^2 (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) = 4 (\\mathbb{A} - \\mathbb{B}) \\mathbf{d}^t\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "或者，等价地，\n",
    "\n",
    "$$\n",
    "(\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) = 4 \\left( (\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) - \\omega^2 \\mathbb{1} \\right)^{-1} (\\mathbb{A} - \\mathbb{B}) \\mathbf{d}^t\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "两边再乘上 $\\mathbf{d}^s$，就得到了含频极化率："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{align}\n",
    "\\alpha_{ts} (\\omega) = 4 \\mathbf{d}^s{}^\\dagger \\left( (\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) - \\omega^2 \\mathbb{1} \\right)^{-1} (\\mathbb{A} - \\mathbb{B}) \\mathbf{d}^t \\tag{7}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以 $\\omega = 0.186 \\, E_\\mathrm{h}$ 来表达上式，则有"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 7.28458, -0.05683, -2.08145],\n",
       "       [-0.05683,  4.79845, -1.39731],\n",
       "       [-2.08145, -1.39731, 37.73368]])"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "4 * np.einsum(\"tP, PR, RQ, sQ -> ts\", d_P, np.linalg.inv((A - B) @ (A + B) - omega**2 * np.eye(nvir*nocc)), A - B, d_P)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上述结果与通过式 (4) 给出的结果一致。\n",
    "\n",
    "下面我们引入一个技巧。我们将会对矩阵 $(\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) - \\omega^2 \\mathbb{1}$ 作逆矩阵分解。我们采用的做法是分析矩阵的特征值与特征向量。依据我们对式 (1) 形式的 Casida 方程的讨论，我们应当容易推知\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "(\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) (\\mathbf{X}^n + \\mathbf{Y}^n) = \\omega_n^2 (\\mathbf{X}^n + \\mathbf{Y}^n) \\tag{8}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose((A - B) @ (A + B) @ (X + Y).T, td_eig**2 * (X + Y).T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们曾经指出过正交条件为 $(\\mathbf{X} + \\mathbf{Y})^\\dagger (\\mathbf{X} - \\mathbf{Y}) = 2 \\cdot \\mathbb{1}$，因此，若将 $\\mathbf{X}, \\mathbf{Y}$ 分别看作是横向维度 $n$ 表示激发态，纵向维度 $P$ 表示激发和退激发分量的方形矩阵：\n",
    "\n",
    "$$\n",
    "\\mathbf{X} = \\begin{pmatrix} \\mathbf{X}^1{}^\\dagger \\\\ \\mathbf{X}^2{}^\\dagger \\\\ \\vdots \\\\ \\mathbf{X}^{n_\\mathrm{occ} n_\\mathrm{vir}}{}^\\dagger \\end{pmatrix}\n",
    "$$\n",
    "\n",
    "那么 $(\\mathbf{X} + \\mathbf{Y})$ 与 $(\\mathbf{X} - \\mathbf{Y})$ 之间存在互逆关系：\n",
    "\n",
    "$$\n",
    "(\\mathbf{X} + \\mathbf{Y})^{-1} = \\frac{1}{2} (\\mathbf{X} - \\mathbf{Y})^\\dagger\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(np.linalg.inv(X + Y), 0.5 * (X - Y).T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，一定程度上，我们可以认为 $(\\mathbf{X} + \\mathbf{Y})$ 与 $(\\mathbf{X} - \\mathbf{Y})$ 两者是相互对偶的向量组。注意区别这里的 $\\mathbf{X}$ 引入的目的是为了刻画 $\\mathbb{A}, \\mathbb{B}$ 的性质，而与 $\\mathbf{X}'{}^t$ 没有直接关联。\n",
    "\n",
    "这种向量组满足下述特征向量分解公式：\n",
    "\n",
    "$$\n",
    "(\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) = \\frac{1}{2} (\\mathbf{X} + \\mathbf{Y}) \\mathbf{\\Omega}^2 (\\mathbf{X} - \\mathbf{Y})^\\dagger\n",
    "$$\n",
    "\n",
    "其中，\n",
    "\n",
    "$$\n",
    "\\mathbf{\\Omega} =\n",
    "\\begin{pmatrix}\n",
    "\\omega_1 & 0 & \\cdots & 0 \\\\\n",
    "0 & \\omega_2 & \\cdots & 0 \\\\\n",
    "\\vdots & \\vdots & \\ddots & \\vdots \\\\\n",
    "0 & 0 & \\cdots & \\omega_{n_\\mathrm{occ} n_\\mathrm{vir}}\n",
    "\\end{pmatrix}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(\n",
    "    0.5 * np.einsum(\"nP, n, nQ -> PQ\", X + Y, td_eig**2, X - Y),\n",
    "    (A - B) @ (A + B))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们来讨论矩阵 $(\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) - \\omega^2 \\mathbb{1}$。我们容易从式 (8) 推得\n",
    "\n",
    "$$\n",
    "\\left( (\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) - \\omega^2 \\mathbb{1} \\right) (\\mathbf{X}^n + \\mathbf{Y}^n) = (\\omega_n^2 - \\omega^2) (\\mathbf{X}^n + \\mathbf{Y}^n)\n",
    "$$\n",
    "\n",
    "即上述方程的本征向量仍然是 $(\\mathbf{X}^n + \\mathbf{Y}^n)$，但本征值却变为 $\\omega_n^2 - \\omega^2$。因此，\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\left( (\\mathbb{A} - \\mathbb{B}) (\\mathbb{A} + \\mathbb{B}) - \\omega^2 \\mathbb{1} \\right)^{-1} = \\left( \\frac{1}{2} (\\mathbf{X} + \\mathbf{Y}) (\\mathbf{\\Omega}^2 - \\omega^2 \\mathbb{1}) (\\mathbf{X} - \\mathbf{Y})^\\dagger \\right)^{-1} = \\frac{1}{2} (\\mathbf{X} + \\mathbf{Y}) (\\mathbf{\\Omega}^2 - \\omega^2 \\mathbb{1})^{-1} (\\mathbf{X} - \\mathbf{Y})^\\dagger \\tag{9}\n",
    "\\end{align}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上式中 $(\\mathbf{\\Omega}^2 - \\omega^2 \\mathbb{1})^{-1}$ 是一个极为容易计算的对角矩阵。$\\omega = 0.186 \\, E_\\mathrm{h}$ 下，程序表示上述过程则为"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(\n",
    "    0.5 * np.einsum(\"nP, n, nQ -> PQ\", X + Y, 1 / (td_eig**2 - omega**2), X - Y),\n",
    "    np.linalg.inv((A - B) @ (A + B) - omega**2 * np.eye(nvir*nocc)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将式 (9) 代入到式 (7)，得到\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega) = 2 \\mathbf{d}^s{}^\\dagger (\\mathbf{X} + \\mathbf{Y}) (\\mathbf{\\Omega}^2 - \\omega^2 \\mathbb{1})^{-1} (\\mathbf{X} - \\mathbf{Y})^\\dagger (\\mathbb{A} - \\mathbb{B}) \\mathbf{d}^t\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "利用极化率的 Hermite 性，并将上述矩阵表达式展开为求和式，得到\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega) = 2 d_P^t (\\mathbf{X}^n + \\mathbf{Y}^n)_P \\cdot \\frac{1}{\\omega_n^2 - \\omega^2} \\cdot (\\mathbf{X}^n - \\mathbf{Y}^n)_R (\\mathbb{A} - \\mathbb{B})_{RQ} d_Q^s\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 7.28458, -0.05683, -2.08145],\n",
       "       [-0.05683,  4.79845, -1.39731],\n",
       "       [-2.08145, -1.39731, 37.73368]])"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "2 * np.einsum(\"tP, nP, n, nR, RQ, sQ\", d_P, X + Y, 1 / (td_eig**2 - omega**2), X - Y, A - B, d_P)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若要化简上式，首先需要利用 $(\\mathbb{A} - \\mathbb{B})_{RQ}$ 事实上恰好是一个 Hermite 矩阵："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(A - B, (A - B).T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "随后注意到 $(\\mathbb{A} - \\mathbb{B})_{QR} (\\mathbf{X}^n - \\mathbf{Y}^n)_R = \\omega_n (\\mathbf{X}^n + \\mathbf{Y}^n)_Q$；于是上式化为\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega) = 2 d_P^t (\\mathbf{X}^n + \\mathbf{Y}^n)_P \\cdot \\frac{\\omega_n}{\\omega_n^2 - \\omega^2} \\cdot (\\mathbf{X}^n + \\mathbf{Y}^n)_Q d_Q^s\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们留意到跃迁偶极矩的定义是 $\\langle 0 | \\hat d{}^t | n \\rangle = d_P^t (\\mathbf{X}^n + \\mathbf{Y}^n)_P$，并且利用下面的小技巧：\n",
    "\n",
    "$$\n",
    "\\frac{\\omega_n}{\\omega_n^2 - \\omega^2} = \\frac{1}{2} \\left( \\frac{1}{\\omega_n - \\omega} - \\frac{1}{\\omega_n + \\omega} \\right)\n",
    "$$\n",
    "\n",
    "我们就可以推知，\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega) = \\frac{\\langle 0 | \\hat d{}^t | n \\rangle \\langle n | \\hat d{}^s | 0 \\rangle}{\\omega_n - \\omega} + \\frac{\\langle 0 | \\hat d{}^t | n \\rangle \\langle n | \\hat d{}^s | 0 \\rangle}{\\omega_n + \\omega}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 7.28458, -0.05683, -2.08145],\n",
       "       [-0.05683,  4.79845, -1.39731],\n",
       "       [-2.08145, -1.39731, 37.73368]])"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(\n",
    "    + np.einsum(\"tP, nP, n, nQ, sQ -> ts\", d_P, X + Y, 1 / (td_eig - omega), X + Y, d_P)\n",
    "    + np.einsum(\"tP, nP, n, nQ, sQ -> ts\", d_P, X + Y, 1 / (td_eig + omega), X + Y, d_P)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这就完成了从普适的 Casida 方程推演得到跃迁偶极矩表示的极化率的公式了。\n",
    "\n",
    "若接受 Casida 方程的假设前提，那么上述的推演将会是严格的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TD-HF 方程含频极化率与 CP-HF 方程间的关系"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们先回顾静态极化率求取时所使用的 CP-HF 方程：\n",
    "\n",
    "$$\n",
    "A'_{ia, jb} U^t_{jb} = d^t_{ia}\n",
    "$$\n",
    "\n",
    "写为双下标的形式，则为\n",
    "\n",
    "$$\n",
    "A'_{PQ} U^t_Q = d^t_P\n",
    "$$\n",
    "\n",
    "写为矩阵形式，则为\n",
    "\n",
    "$$\n",
    "\\mathbf{A}' \\mathbf{U}^t = \\mathbf{d}^t\n",
    "$$\n",
    "\n",
    "留意到 $A'_{PQ} = (\\mathbb{A} + \\mathbb{B})_{PQ}$，因此上式这对应到 Casida 方程的一个导出式 (5)。若 $\\omega = 0$，则\n",
    "\n",
    "$$\n",
    "(\\mathbb{A} + \\mathbb{B}) (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) = 4 \\mathbf{d}^t\n",
    "$$\n",
    "\n",
    "因此，在静态情形 $\\omega = 0$ 下，$\\mathbf{U}^t = \\frac{1}{4} (\\mathbf{X}' + \\mathbf{Y}')$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但若 $\\omega \\neq 0$，那么式 (5) 应写作\n",
    "\n",
    "$$\n",
    "\\left( (\\mathbb{A} + \\mathbb{B}) - \\omega^2 (\\mathbb{A} - \\mathbb{B})^{-1} \\right) (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) = 4 \\mathbf{d}^t\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若我们拓展 CP-HF 方程为含频形式\n",
    "\n",
    "$$\n",
    "\\mathbf{A}' (\\omega) \\mathbf{U}^t (\\omega) = \\mathbf{d}^t\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "并且极化率可以写为\n",
    "\n",
    "$$\n",
    "\\alpha_{ts} (\\omega) = 4 \\mathbf{U}^t (\\omega)^\\dagger \\mathbf{d}^t\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\mathbf{A}' (\\omega) &= (\\mathbb{A} + \\mathbb{B}) - \\omega^2 (\\mathbb{A} - \\mathbb{B})^{-1} \\\\\n",
    "\\mathbf{U}^t (\\omega) &= \\frac{1}{4} (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t)\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "需要留意尽管我们之前一直没有引入 $(\\omega)$ 记号来强调，但 $\\mathbf{X}'{}^t, \\mathbf{Y}'{}^t$ 是随频率变化而变化的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 7.28458, -0.05683, -2.08145],\n",
       "       [-0.05683,  4.79845, -1.39731],\n",
       "       [-2.08145, -1.39731, 37.73368]])"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "omega = 0.186\n",
    "A_p_omega = (A + B) - omega**2 * np.linalg.inv(A - B)\n",
    "U_omega = np.einsum(\"PQ, tQ -> tP\", np.linalg.inv(A_p_omega), d_P)\n",
    "4 * np.einsum(\"tP, sP -> ts\", U_omega, d_P)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这就在含频情形下，将 CP-HF 与 TD-HF 的公式联系在了一起。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 总结"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这篇文档我们简单且不太严格和完整地回顾了静态与含频极化率的计算，通过 Casida 方程推导了含频极化率，并将 TD-HF 分析方法与 CP-HF 方法联系起来。一些主要的结论和拓展思路会是："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- TD-HF 方程与 CP-HF 方程在静态极化率情形下有极为紧密的联系，两种表达式完全等价；而含频极化率情形下，TD-HF 方程 (Casida 方程) 可以导出与 CP-HF 方程形式类似的方程。这可能是 Frequency-Dependent CP-HF 方程的原型。\n",
    "\n",
    "- 从 CP 的角度讲，根据下式\n",
    "\n",
    "    $$\n",
    "    (\\mathbb{A} + \\mathbb{B}) (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t) = 4 \\mathbf{d}^t + \\omega (\\mathbb{A} - \\mathbb{B})^{-1} (\\mathbf{X}'{}^t + \\mathbf{Y}'{}^t)\n",
    "    $$\n",
    "    \n",
    "    或\n",
    "    \n",
    "    $$\n",
    "    \\mathbf{A}' (\\omega) \\mathbf{U}^t (\\omega) = \\mathbf{d}^t\n",
    "    $$\n",
    "\n",
    "    我们可以想到等式左边是电子云的弛豫过程；等式右边是电子云所受到的外加微扰场。因此电子云的激发过程可以看作外场微扰。\n",
    "    \n",
    "    若退化为没有偶极微扰的分子激发过程，从 CP 的角度看，电子云变形的弛豫效应 (等式左边) 应当恰好能补足电子云激发所导致的变形外场 (等式右边)。当两者自洽时，电子云能态的激发成立。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 上述所述的 TD-HF 方程是 TD 分析中的 Linear Response 关系。事实上，TD 还具有更高阶的分析过程，但这里我们并没有涉及。\n",
    "\n",
    "    一个显而易见的问题会是，电子云已经在外加偶极场中，因此电子云的性质应当已经发生变化；但 Linear Response 给出的 TD-HF 方程 (Casida 方程) 却告诉我们体系的激发能 $\\omega_n$ 并没有变化，无论偶极场的值有多大。这显然违背我们的物理直觉。这也可能意味着以 MP2、CC 为底层量化方法，若外加的相关能密度微扰近似场没有把握住物理实在，那么含频极化率也可能面临激发能 $\\omega_n$ 还仍然是 TD-HF 激发能的情况。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 文档中没有画出，但确实存在的情况是，若入射频率恰好处在分子的激发态密集区，那么单个入射频率下的含频极化率的计算很可能是没有意义的；因为在激发态密集区，含频极化率曲线的振荡情况及其严重，不仅近乎毫无规律可言，其绝对值也大到非常离谱。\n",
    "\n",
    "    Raman 光谱可以认为是含频极化率对分子的简正坐标导数得来。从实验的角度来讲，这可能是表面增强 Raman (SERS) 中化学增强效应的一个很好的性质。若有机物负载在银原子簇上，入射的激发光处于银原子簇的激发能带但又不破坏有机物分子，或让银原子簇与有机物分子产生电荷转移时，Raman 信号确实可以成千上万倍地增强。但从计算的角度来讲，这可能是非常令人绝望的，因为计算过程中的误差把控近乎于不存在；若使用不同的密度泛函近似，即使得到相差 0.1 eV 的激发能带差，即使定性上结果正确，但计算得到的 Raman 信号也可能会有成百上千的误差。\n",
    "    \n",
    "    因此，这可能需要我们对入射光作某种频率增宽，以平缓极化率断点，得到相对定量的数值结果；或者将极化率当作原子可加性的物理性质，进行类似于 AIM (Atomic in Molecule) 的极化率拆分的定性近似分析。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[^Valley-Schatz.JPCL.2013]: Valley, N.; Greeneltch, N.; Duyne, R. P. V.; Schatz, G. C. A Look at the Origin and Magnitude of the Chemical Contribution to the Enhancement Mechanism of Surface-Enhanced Raman Spectroscopy (SERS): Theory and Experiment. *J. Phys. Chem. Lett.* **2013**, *4* (16), 2599–2604. doi: [10.1021/jz4012383](https://doi.org/10.1021/jz4012383)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
